diff --git a/recipes-kernel/linux/linux-pinephonepro/0001-base-property-Swap-order-of-search-for-connection-to.patch b/recipes-kernel/linux/linux-pinephonepro/0001-base-property-Swap-order-of-search-for-connection-to.patch new file mode 100644 index 0000000..8a3b62c --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/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/recipes-kernel/linux/linux-pinephonepro/0001-bootsplash.patch b/recipes-kernel/linux/linux-pinephonepro/0001-bootsplash.patch new file mode 100644 index 0000000..d5835e5 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0001-bootsplash.patch @@ -0,0 +1,746 @@ +diff --git a/MAINTAINERS b/MAINTAINERS +index a74227ad082e..b5633b56391e 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -2705,6 +2705,14 @@ S: Supported + F: drivers/net/bonding/ + F: include/uapi/linux/if_bonding.h + ++BOOTSPLASH ++M: Max Staudt ++L: linux-fbdev@vger.kernel.org ++S: Maintained ++F: drivers/video/fbdev/core/bootsplash*.* ++F: drivers/video/fbdev/core/dummycon.c ++F: include/linux/bootsplash.h ++ + BPF (Safe dynamic programs and tools) + M: Alexei Starovoitov + M: Daniel Borkmann +diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig +index 7f1f1fbcef9e..f3ff976266fe 100644 +--- a/drivers/video/console/Kconfig ++++ b/drivers/video/console/Kconfig +@@ -151,6 +151,30 @@ config FRAMEBUFFER_CONSOLE_ROTATION + such that other users of the framebuffer will remain normally + oriented. + ++config BOOTSPLASH ++ bool "Bootup splash screen" ++ depends on FRAMEBUFFER_CONSOLE ++ help ++ This option enables the Linux bootsplash screen. ++ ++ The bootsplash is a full-screen logo or animation indicating a ++ booting system. It replaces the classic scrolling text with a ++ graphical alternative, similar to other systems. ++ ++ Since this is technically implemented as a hook on top of fbcon, ++ it can only work if the FRAMEBUFFER_CONSOLE is enabled and a ++ framebuffer driver is active. Thus, to get a text-free boot, ++ the system needs to boot with vesafb, efifb, or similar. ++ ++ Once built into the kernel, the bootsplash needs to be enabled ++ with bootsplash.enabled=1 and a splash file needs to be supplied. ++ ++ Further documentation can be found in: ++ Documentation/fb/bootsplash.txt ++ ++ If unsure, say N. ++ This is typically used by distributors and system integrators. ++ + config STI_CONSOLE + bool "STI text console" + depends on PARISC +diff --git a/drivers/video/fbdev/core/Makefile b/drivers/video/fbdev/core/Makefile +index 73493bbd7a15..66895321928e 100644 +--- a/drivers/video/fbdev/core/Makefile ++++ b/drivers/video/fbdev/core/Makefile +@@ -29,3 +29,6 @@ obj-$(CONFIG_FB_SYS_IMAGEBLIT) += sysimgblt.o + obj-$(CONFIG_FB_SYS_FOPS) += fb_sys_fops.o + obj-$(CONFIG_FB_SVGALIB) += svgalib.o + obj-$(CONFIG_FB_DDC) += fb_ddc.o ++ ++obj-$(CONFIG_BOOTSPLASH) += bootsplash.o bootsplash_render.o \ ++ dummyblit.o +diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c +new file mode 100644 +index 000000000000..e449755af268 +--- /dev/null ++++ b/drivers/video/fbdev/core/bootsplash.c +@@ -0,0 +1,294 @@ ++/* ++ * Kernel based bootsplash. ++ * ++ * (Main file: Glue code, workers, timer, PM, kernel and userland API) ++ * ++ * Authors: ++ * Max Staudt ++ * ++ * SPDX-License-Identifier: GPL-2.0 ++ */ ++ ++#define pr_fmt(fmt) "bootsplash: " fmt ++ ++ ++#include ++#include ++#include ++#include /* dev_warn() */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include /* console_blanked */ ++#include ++#include ++#include ++#include ++#include ++ ++#include "bootsplash_internal.h" ++ ++ ++/* ++ * We only have one splash screen, so let's keep a single ++ * instance of the internal state. ++ */ ++static struct splash_priv splash_state; ++ ++ ++static void splash_callback_redraw_vc(struct work_struct *ignored) ++{ ++ if (console_blanked) ++ return; ++ ++ console_lock(); ++ if (vc_cons[fg_console].d) ++ update_screen(vc_cons[fg_console].d); ++ console_unlock(); ++} ++ ++ ++static bool is_fb_compatible(const struct fb_info *info) ++{ ++ if (!(info->flags & FBINFO_BE_MATH) ++ != !fb_be_math((struct fb_info *)info)) { ++ dev_warn(info->device, ++ "Can't draw on foreign endianness framebuffer.\n"); ++ ++ return false; ++ } ++ ++ if (info->flags & FBINFO_MISC_TILEBLITTING) { ++ dev_warn(info->device, ++ "Can't draw splash on tiling framebuffer.\n"); ++ ++ return false; ++ } ++ ++ if (info->fix.type != FB_TYPE_PACKED_PIXELS ++ || (info->fix.visual != FB_VISUAL_TRUECOLOR ++ && info->fix.visual != FB_VISUAL_DIRECTCOLOR)) { ++ dev_warn(info->device, ++ "Can't draw splash on non-packed or non-truecolor framebuffer.\n"); ++ ++ dev_warn(info->device, ++ " type: %u visual: %u\n", ++ info->fix.type, info->fix.visual); ++ ++ return false; ++ } ++ ++ if (info->var.bits_per_pixel != 16 ++ && info->var.bits_per_pixel != 24 ++ && info->var.bits_per_pixel != 32) { ++ dev_warn(info->device, ++ "We only support drawing on framebuffers with 16, 24, or 32 bpp, not %d.\n", ++ info->var.bits_per_pixel); ++ ++ return false; ++ } ++ ++ return true; ++} ++ ++ ++/* ++ * Called by fbcon_switch() when an instance is activated or refreshed. ++ */ ++void bootsplash_render_full(struct fb_info *info) ++{ ++ if (!is_fb_compatible(info)) ++ return; ++ ++ bootsplash_do_render_background(info); ++} ++ ++ ++/* ++ * External status enquiry and on/off switch ++ */ ++bool bootsplash_would_render_now(void) ++{ ++ return !oops_in_progress ++ && !console_blanked ++ && bootsplash_is_enabled(); ++} ++ ++bool bootsplash_is_enabled(void) ++{ ++ bool was_enabled; ++ ++ /* Make sure we have the newest state */ ++ smp_rmb(); ++ ++ was_enabled = test_bit(0, &splash_state.enabled); ++ ++ return was_enabled; ++} ++ ++void bootsplash_disable(void) ++{ ++ int was_enabled; ++ ++ was_enabled = test_and_clear_bit(0, &splash_state.enabled); ++ ++ if (was_enabled) { ++ if (oops_in_progress) { ++ /* Redraw screen now so we can see a panic */ ++ if (vc_cons[fg_console].d) ++ update_screen(vc_cons[fg_console].d); ++ } else { ++ /* No urgency, redraw at next opportunity */ ++ schedule_work(&splash_state.work_redraw_vc); ++ } ++ } ++} ++ ++void bootsplash_enable(void) ++{ ++ bool was_enabled; ++ ++ if (oops_in_progress) ++ return; ++ ++ was_enabled = test_and_set_bit(0, &splash_state.enabled); ++ ++ if (!was_enabled) ++ schedule_work(&splash_state.work_redraw_vc); ++} ++ ++ ++/* ++ * Userland API via platform device in sysfs ++ */ ++static ssize_t splash_show_enabled(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return sprintf(buf, "%d\n", bootsplash_is_enabled()); ++} ++ ++static ssize_t splash_store_enabled(struct device *device, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ bool enable; ++ int err; ++ ++ if (!buf || !count) ++ return -EFAULT; ++ ++ err = kstrtobool(buf, &enable); ++ if (err) ++ return err; ++ ++ if (enable) ++ bootsplash_enable(); ++ else ++ bootsplash_disable(); ++ ++ return count; ++} ++ ++static DEVICE_ATTR(enabled, 0644, splash_show_enabled, splash_store_enabled); ++ ++ ++static struct attribute *splash_dev_attrs[] = { ++ &dev_attr_enabled.attr, ++ NULL ++}; ++ ++ATTRIBUTE_GROUPS(splash_dev); ++ ++ ++ ++ ++/* ++ * Power management fixup via platform device ++ * ++ * When the system is woken from sleep or restored after hibernating, we ++ * cannot expect the screen contents to still be present in video RAM. ++ * Thus, we have to redraw the splash if we're currently active. ++ */ ++static int splash_resume(struct device *device) ++{ ++ if (bootsplash_would_render_now()) ++ schedule_work(&splash_state.work_redraw_vc); ++ ++ return 0; ++} ++ ++static int splash_suspend(struct device *device) ++{ ++ cancel_work_sync(&splash_state.work_redraw_vc); ++ ++ return 0; ++} ++ ++ ++static const struct dev_pm_ops splash_pm_ops = { ++ .thaw = splash_resume, ++ .restore = splash_resume, ++ .resume = splash_resume, ++ .suspend = splash_suspend, ++ .freeze = splash_suspend, ++}; ++ ++static struct platform_driver splash_driver = { ++ .driver = { ++ .name = "bootsplash", ++ .pm = &splash_pm_ops, ++ }, ++}; ++ ++ ++/* ++ * Main init ++ */ ++void bootsplash_init(void) ++{ ++ int ret; ++ ++ /* Initialized already? */ ++ if (splash_state.splash_device) ++ return; ++ ++ ++ /* Register platform device to export user API */ ++ ret = platform_driver_register(&splash_driver); ++ if (ret) { ++ pr_err("platform_driver_register() failed: %d\n", ret); ++ goto err; ++ } ++ ++ splash_state.splash_device ++ = platform_device_alloc("bootsplash", 0); ++ ++ if (!splash_state.splash_device) ++ goto err_driver; ++ ++ splash_state.splash_device->dev.groups = splash_dev_groups; ++ ++ ret = platform_device_add(splash_state.splash_device); ++ if (ret) { ++ pr_err("platform_device_add() failed: %d\n", ret); ++ goto err_device; ++ } ++ ++ ++ INIT_WORK(&splash_state.work_redraw_vc, splash_callback_redraw_vc); ++ ++ return; ++ ++err_device: ++ platform_device_put(splash_state.splash_device); ++ splash_state.splash_device = NULL; ++err_driver: ++ platform_driver_unregister(&splash_driver); ++err: ++ pr_err("Failed to initialize.\n"); ++} +diff --git a/drivers/video/fbdev/core/bootsplash_internal.h b/drivers/video/fbdev/core/bootsplash_internal.h +new file mode 100644 +index 000000000000..b11da5cb90bf +--- /dev/null ++++ b/drivers/video/fbdev/core/bootsplash_internal.h +@@ -0,0 +1,55 @@ ++/* ++ * Kernel based bootsplash. ++ * ++ * (Internal data structures used at runtime) ++ * ++ * Authors: ++ * Max Staudt ++ * ++ * SPDX-License-Identifier: GPL-2.0 ++ */ ++ ++#ifndef __BOOTSPLASH_INTERNAL_H ++#define __BOOTSPLASH_INTERNAL_H ++ ++ ++#include ++#include ++#include ++#include ++#include ++ ++ ++/* ++ * Runtime types ++ */ ++struct splash_priv { ++ /* ++ * Enabled/disabled state, to be used with atomic bit operations. ++ * Bit 0: 0 = Splash hidden ++ * 1 = Splash shown ++ * ++ * Note: fbcon.c uses this twice, by calling ++ * bootsplash_would_render_now() in set_blitting_type() and ++ * in fbcon_switch(). ++ * This is racy, but eventually consistent: Turning the ++ * splash on/off will cause a redraw, which calls ++ * fbcon_switch(), which calls set_blitting_type(). ++ * So the last on/off toggle will make things consistent. ++ */ ++ unsigned long enabled; ++ ++ /* Our gateway to userland via sysfs */ ++ struct platform_device *splash_device; ++ ++ struct work_struct work_redraw_vc; ++}; ++ ++ ++ ++/* ++ * Rendering functions ++ */ ++void bootsplash_do_render_background(struct fb_info *info); ++ ++#endif +diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c +new file mode 100644 +index 000000000000..4d7e0117f653 +--- /dev/null ++++ b/drivers/video/fbdev/core/bootsplash_render.c +@@ -0,0 +1,93 @@ ++/* ++ * Kernel based bootsplash. ++ * ++ * (Rendering functions) ++ * ++ * Authors: ++ * Max Staudt ++ * ++ * SPDX-License-Identifier: GPL-2.0 ++ */ ++ ++#define pr_fmt(fmt) "bootsplash: " fmt ++ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "bootsplash_internal.h" ++ ++ ++ ++ ++/* ++ * Rendering: Internal drawing routines ++ */ ++ ++ ++/* ++ * Pack pixel into target format and do Big/Little Endian handling. ++ * This would be a good place to handle endianness conversion if necessary. ++ */ ++static inline u32 pack_pixel(const struct fb_var_screeninfo *dst_var, ++ u8 red, u8 green, u8 blue) ++{ ++ u32 dstpix; ++ ++ /* Quantize pixel */ ++ red = red >> (8 - dst_var->red.length); ++ green = green >> (8 - dst_var->green.length); ++ blue = blue >> (8 - dst_var->blue.length); ++ ++ /* Pack pixel */ ++ dstpix = red << (dst_var->red.offset) ++ | green << (dst_var->green.offset) ++ | blue << (dst_var->blue.offset); ++ ++ /* ++ * Move packed pixel to the beginning of the memory cell, ++ * so we can memcpy() it out easily ++ */ ++#ifdef __BIG_ENDIAN ++ switch (dst_var->bits_per_pixel) { ++ case 16: ++ dstpix <<= 16; ++ break; ++ case 24: ++ dstpix <<= 8; ++ break; ++ case 32: ++ break; ++ } ++#else ++ /* This is intrinsically unnecessary on Little Endian */ ++#endif ++ ++ return dstpix; ++} ++ ++ ++void bootsplash_do_render_background(struct fb_info *info) ++{ ++ unsigned int x, y; ++ u32 dstpix; ++ u32 dst_octpp = info->var.bits_per_pixel / 8; ++ ++ dstpix = pack_pixel(&info->var, ++ 0, ++ 0, ++ 0); ++ ++ for (y = 0; y < info->var.yres_virtual; y++) { ++ u8 *dstline = info->screen_buffer + (y * info->fix.line_length); ++ ++ for (x = 0; x < info->var.xres_virtual; x++) { ++ memcpy(dstline, &dstpix, dst_octpp); ++ ++ dstline += dst_octpp; ++ } ++ } ++} +diff --git a/drivers/video/fbdev/core/dummyblit.c b/drivers/video/fbdev/core/dummyblit.c +new file mode 100644 +index 000000000000..8c22ff92ce24 +--- /dev/null ++++ b/drivers/video/fbdev/core/dummyblit.c +@@ -0,0 +1,89 @@ ++/* ++ * linux/drivers/video/fbdev/core/dummyblit.c -- Dummy Blitting Operation ++ * ++ * Authors: ++ * Max Staudt ++ * ++ * These functions are used in place of blitblit/tileblit to suppress ++ * fbcon's text output while a splash is shown. ++ * ++ * Only suppressing actual rendering keeps the text buffer in the VC layer ++ * intact and makes it easy to switch back from the bootsplash to a full ++ * text console with a simple redraw (with the original functions in place). ++ * ++ * Based on linux/drivers/video/fbdev/core/bitblit.c ++ * and linux/drivers/video/fbdev/core/tileblit.c ++ * ++ * SPDX-License-Identifier: GPL-2.0 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include "fbcon.h" ++ ++static void dummy_bmove(struct vc_data *vc, struct fb_info *info, int sy, ++ int sx, int dy, int dx, int height, int width) ++{ ++ ; ++} ++ ++static void dummy_clear(struct vc_data *vc, struct fb_info *info, int sy, ++ int sx, int height, int width) ++{ ++ ; ++} ++ ++static void dummy_putcs(struct vc_data *vc, struct fb_info *info, ++ const unsigned short *s, int count, int yy, int xx, ++ int fg, int bg) ++{ ++ ; ++} ++ ++static void dummy_clear_margins(struct vc_data *vc, struct fb_info *info, ++ int color, int bottom_only) ++{ ++ ; ++} ++ ++static void dummy_cursor(struct vc_data *vc, struct fb_info *info, int mode, ++ int softback_lines, int fg, int bg) ++{ ++ ; ++} ++ ++static int dummy_update_start(struct fb_info *info) ++{ ++ /* ++ * Copied from bitblit.c and tileblit.c ++ * ++ * As of Linux 4.12, nobody seems to care about our return value. ++ */ ++ struct fbcon_ops *ops = info->fbcon_par; ++ int err; ++ ++ err = fb_pan_display(info, &ops->var); ++ ops->var.xoffset = info->var.xoffset; ++ ops->var.yoffset = info->var.yoffset; ++ ops->var.vmode = info->var.vmode; ++ return err; ++} ++ ++void fbcon_set_dummyops(struct fbcon_ops *ops) ++{ ++ ops->bmove = dummy_bmove; ++ ops->clear = dummy_clear; ++ ops->putcs = dummy_putcs; ++ ops->clear_margins = dummy_clear_margins; ++ ops->cursor = dummy_cursor; ++ ops->update_start = dummy_update_start; ++ ops->rotate_font = NULL; ++} ++EXPORT_SYMBOL_GPL(fbcon_set_dummyops); ++ ++MODULE_AUTHOR("Max Staudt "); ++MODULE_DESCRIPTION("Dummy Blitting Operation"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c +index 04612f938bab..9a39a6fcfe98 100644 +--- a/drivers/video/fbdev/core/fbcon.c ++++ b/drivers/video/fbdev/core/fbcon.c +@@ -80,6 +80,7 @@ + #include + + #include "fbcon.h" ++#include + + #ifdef FBCONDEBUG + # define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt, __func__ , ## args) +@@ -542,6 +543,8 @@ static int do_fbcon_takeover(int show_logo) + for (i = first_fb_vc; i <= last_fb_vc; i++) + con2fb_map[i] = info_idx; + ++ bootsplash_init(); ++ + err = do_take_over_console(&fb_con, first_fb_vc, last_fb_vc, + fbcon_is_default); + +@@ -661,6 +664,9 @@ static void set_blitting_type(struct vc_data *vc, struct fb_info *info) + else { + fbcon_set_rotation(info); + fbcon_set_bitops(ops); ++ ++ if (bootsplash_would_render_now()) ++ fbcon_set_dummyops(ops); + } + } + +@@ -683,6 +689,19 @@ static void set_blitting_type(struct vc_data *vc, struct fb_info *info) + ops->p = &fb_display[vc->vc_num]; + fbcon_set_rotation(info); + fbcon_set_bitops(ops); ++ ++ /* ++ * Note: ++ * This is *eventually correct*. ++ * Setting the fbcon operations and drawing the splash happen at ++ * different points in time. If the splash is enabled/disabled ++ * in between, then bootsplash_{en,dis}able will schedule a ++ * redraw, which will again render the splash (or not) and set ++ * the correct fbcon ops. ++ * The last run will then be the right one. ++ */ ++ if (bootsplash_would_render_now()) ++ fbcon_set_dummyops(ops); + } + + static int fbcon_invalid_charcount(struct fb_info *info, unsigned charcount) +@@ -2184,6 +2203,9 @@ static int fbcon_switch(struct vc_data *vc) + info = registered_fb[con2fb_map[vc->vc_num]]; + ops = info->fbcon_par; + ++ if (bootsplash_would_render_now()) ++ bootsplash_render_full(info); ++ + if (softback_top) { + if (softback_lines) + fbcon_set_origin(vc); +diff --git a/drivers/video/fbdev/core/fbcon.h b/drivers/video/fbdev/core/fbcon.h +index 18f3ac144237..45f94347fe5e 100644 +--- a/drivers/video/fbdev/core/fbcon.h ++++ b/drivers/video/fbdev/core/fbcon.h +@@ -214,6 +214,11 @@ static inline int attr_col_ec(int shift, struct vc_data *vc, + #define SCROLL_REDRAW 0x004 + #define SCROLL_PAN_REDRAW 0x005 + ++#ifdef CONFIG_BOOTSPLASH ++extern void fbcon_set_dummyops(struct fbcon_ops *ops); ++#else /* CONFIG_BOOTSPLASH */ ++#define fbcon_set_dummyops(x) ++#endif /* CONFIG_BOOTSPLASH */ + #ifdef CONFIG_FB_TILEBLITTING + extern void fbcon_set_tileops(struct vc_data *vc, struct fb_info *info); + #endif +diff --git a/include/linux/bootsplash.h b/include/linux/bootsplash.h +new file mode 100644 +index 000000000000..c6dd0b43180d +--- /dev/null ++++ b/include/linux/bootsplash.h +@@ -0,0 +1,43 @@ ++/* ++ * Kernel based bootsplash. ++ * ++ * Authors: ++ * Max Staudt ++ * ++ * SPDX-License-Identifier: GPL-2.0 ++ */ ++ ++#ifndef __LINUX_BOOTSPLASH_H ++#define __LINUX_BOOTSPLASH_H ++ ++#include ++ ++ ++#ifdef CONFIG_BOOTSPLASH ++ ++extern void bootsplash_render_full(struct fb_info *info); ++ ++extern bool bootsplash_would_render_now(void); ++ ++extern bool bootsplash_is_enabled(void); ++extern void bootsplash_disable(void); ++extern void bootsplash_enable(void); ++ ++extern void bootsplash_init(void); ++ ++#else /* CONFIG_BOOTSPLASH */ ++ ++#define bootsplash_render_full(x) ++ ++#define bootsplash_would_render_now() (false) ++ ++#define bootsplash_is_enabled() (false) ++#define bootsplash_disable() ++#define bootsplash_enable() ++ ++#define bootsplash_init() ++ ++#endif /* CONFIG_BOOTSPLASH */ ++ ++ ++#endif diff --git a/recipes-kernel/linux/linux-pinephonepro/0001-revert-garbage-collect-fbdev-scrolling-acceleration.patch b/recipes-kernel/linux/linux-pinephonepro/0001-revert-garbage-collect-fbdev-scrolling-acceleration.patch new file mode 100644 index 0000000..280ed96 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0001-revert-garbage-collect-fbdev-scrolling-acceleration.patch @@ -0,0 +1,1038 @@ +--- b/Documentation/gpu/todo.rst ++++ a/Documentation/gpu/todo.rst +@@ -314,19 +314,16 @@ + Garbage collect fbdev scrolling acceleration + -------------------------------------------- + ++Scroll acceleration is disabled in fbcon by hard-wiring p->scrollmode = ++SCROLL_REDRAW. There's a ton of code this will allow us to remove: +-Scroll acceleration has been disabled in fbcon. Now it works as the old +-SCROLL_REDRAW mode. A ton of code was removed in fbcon.c and the hook bmove was +-removed from fbcon_ops. +-Remaining tasks: + ++- lots of code in fbcon.c ++ ++- a bunch of the hooks in fbcon_ops, maybe the remaining hooks could be called +-- a bunch of the hooks in fbcon_ops could be removed or simplified by calling + directly instead of the function table (with a switch on p->rotate) + + - fb_copyarea is unused after this, and can be deleted from all drivers + +-- after that, fb_copyarea can be deleted from fb_ops in include/linux/fb.h as +- well as cfb_copyarea +- + Note that not all acceleration code can be deleted, since clearing and cursor + support is still accelerated, which might be good candidates for further + deletion projects. +--- b/drivers/video/fbdev/core/bitblit.c ++++ a/drivers/video/fbdev/core/bitblit.c +@@ -43,6 +43,21 @@ + } + } + ++static void bit_bmove(struct vc_data *vc, struct fb_info *info, int sy, ++ int sx, int dy, int dx, int height, int width) ++{ ++ struct fb_copyarea area; ++ ++ area.sx = sx * vc->vc_font.width; ++ area.sy = sy * vc->vc_font.height; ++ area.dx = dx * vc->vc_font.width; ++ area.dy = dy * vc->vc_font.height; ++ area.height = height * vc->vc_font.height; ++ area.width = width * vc->vc_font.width; ++ ++ info->fbops->fb_copyarea(info, &area); ++} ++ + static void bit_clear(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int height, int width) + { +@@ -378,6 +393,7 @@ + + void fbcon_set_bitops(struct fbcon_ops *ops) + { ++ ops->bmove = bit_bmove; + ops->clear = bit_clear; + ops->putcs = bit_putcs; + ops->clear_margins = bit_clear_margins; +--- b/drivers/video/fbdev/core/fbcon.c ++++ a/drivers/video/fbdev/core/fbcon.c +@@ -173,6 +173,8 @@ + int count, int ypos, int xpos); + static void fbcon_clear_margins(struct vc_data *vc, int bottom_only); + static void fbcon_cursor(struct vc_data *vc, int mode); ++static void fbcon_bmove(struct vc_data *vc, int sy, int sx, int dy, int dx, ++ int height, int width); + static int fbcon_switch(struct vc_data *vc); + static int fbcon_blank(struct vc_data *vc, int blank, int mode_switch); + static void fbcon_set_palette(struct vc_data *vc, const unsigned char *table); +@@ -180,8 +182,16 @@ + /* + * Internal routines + */ ++static __inline__ void ywrap_up(struct vc_data *vc, int count); ++static __inline__ void ywrap_down(struct vc_data *vc, int count); ++static __inline__ void ypan_up(struct vc_data *vc, int count); ++static __inline__ void ypan_down(struct vc_data *vc, int count); ++static void fbcon_bmove_rec(struct vc_data *vc, struct fbcon_display *p, int sy, int sx, ++ int dy, int dx, int height, int width, u_int y_break); + static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var, + int unit); ++static void fbcon_redraw_move(struct vc_data *vc, struct fbcon_display *p, ++ int line, int count, int dy); + static void fbcon_modechanged(struct fb_info *info); + static void fbcon_set_all_vcs(struct fb_info *info); + static void fbcon_start(void); +@@ -1125,6 +1135,14 @@ + + ops->graphics = 0; + ++ /* ++ * No more hw acceleration for fbcon. ++ * ++ * FIXME: Garbage collect all the now dead code after sufficient time ++ * has passed. ++ */ ++ p->scrollmode = SCROLL_REDRAW; ++ + /* + * ++guenther: console.c:vc_allocate() relies on initializing + * vc_{cols,rows}, but we must not set those if we are only +@@ -1211,13 +1229,14 @@ + * This system is now divided into two levels because of complications + * caused by hardware scrolling. Top level functions: + * ++ * fbcon_bmove(), fbcon_clear(), fbcon_putc(), fbcon_clear_margins() +- * fbcon_clear(), fbcon_putc(), fbcon_clear_margins() + * + * handles y values in range [0, scr_height-1] that correspond to real + * screen positions. y_wrap shift means that first line of bitmap may be + * anywhere on this display. These functions convert lineoffsets to + * bitmap offsets and deal with the wrap-around case by splitting blits. + * ++ * fbcon_bmove_physical_8() -- These functions fast implementations + * fbcon_clear_physical_8() -- of original fbcon_XXX fns. + * fbcon_putc_physical_8() -- (font width != 8) may be added later + * +@@ -1390,6 +1409,224 @@ + } + } + ++static __inline__ void ywrap_up(struct vc_data *vc, int count) ++{ ++ struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; ++ struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_display *p = &fb_display[vc->vc_num]; ++ ++ p->yscroll += count; ++ if (p->yscroll >= p->vrows) /* Deal with wrap */ ++ p->yscroll -= p->vrows; ++ ops->var.xoffset = 0; ++ ops->var.yoffset = p->yscroll * vc->vc_font.height; ++ ops->var.vmode |= FB_VMODE_YWRAP; ++ ops->update_start(info); ++ scrollback_max += count; ++ if (scrollback_max > scrollback_phys_max) ++ scrollback_max = scrollback_phys_max; ++ scrollback_current = 0; ++} ++ ++static __inline__ void ywrap_down(struct vc_data *vc, int count) ++{ ++ struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; ++ struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_display *p = &fb_display[vc->vc_num]; ++ ++ p->yscroll -= count; ++ if (p->yscroll < 0) /* Deal with wrap */ ++ p->yscroll += p->vrows; ++ ops->var.xoffset = 0; ++ ops->var.yoffset = p->yscroll * vc->vc_font.height; ++ ops->var.vmode |= FB_VMODE_YWRAP; ++ ops->update_start(info); ++ scrollback_max -= count; ++ if (scrollback_max < 0) ++ scrollback_max = 0; ++ scrollback_current = 0; ++} ++ ++static __inline__ void ypan_up(struct vc_data *vc, int count) ++{ ++ struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; ++ struct fbcon_display *p = &fb_display[vc->vc_num]; ++ struct fbcon_ops *ops = info->fbcon_par; ++ ++ p->yscroll += count; ++ if (p->yscroll > p->vrows - vc->vc_rows) { ++ ops->bmove(vc, info, p->vrows - vc->vc_rows, ++ 0, 0, 0, vc->vc_rows, vc->vc_cols); ++ p->yscroll -= p->vrows - vc->vc_rows; ++ } ++ ++ ops->var.xoffset = 0; ++ ops->var.yoffset = p->yscroll * vc->vc_font.height; ++ ops->var.vmode &= ~FB_VMODE_YWRAP; ++ ops->update_start(info); ++ fbcon_clear_margins(vc, 1); ++ scrollback_max += count; ++ if (scrollback_max > scrollback_phys_max) ++ scrollback_max = scrollback_phys_max; ++ scrollback_current = 0; ++} ++ ++static __inline__ void ypan_up_redraw(struct vc_data *vc, int t, int count) ++{ ++ struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; ++ struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_display *p = &fb_display[vc->vc_num]; ++ ++ p->yscroll += count; ++ ++ if (p->yscroll > p->vrows - vc->vc_rows) { ++ p->yscroll -= p->vrows - vc->vc_rows; ++ fbcon_redraw_move(vc, p, t + count, vc->vc_rows - count, t); ++ } ++ ++ ops->var.xoffset = 0; ++ ops->var.yoffset = p->yscroll * vc->vc_font.height; ++ ops->var.vmode &= ~FB_VMODE_YWRAP; ++ ops->update_start(info); ++ fbcon_clear_margins(vc, 1); ++ scrollback_max += count; ++ if (scrollback_max > scrollback_phys_max) ++ scrollback_max = scrollback_phys_max; ++ scrollback_current = 0; ++} ++ ++static __inline__ void ypan_down(struct vc_data *vc, int count) ++{ ++ struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; ++ struct fbcon_display *p = &fb_display[vc->vc_num]; ++ struct fbcon_ops *ops = info->fbcon_par; ++ ++ p->yscroll -= count; ++ if (p->yscroll < 0) { ++ ops->bmove(vc, info, 0, 0, p->vrows - vc->vc_rows, ++ 0, vc->vc_rows, vc->vc_cols); ++ p->yscroll += p->vrows - vc->vc_rows; ++ } ++ ++ ops->var.xoffset = 0; ++ ops->var.yoffset = p->yscroll * vc->vc_font.height; ++ ops->var.vmode &= ~FB_VMODE_YWRAP; ++ ops->update_start(info); ++ fbcon_clear_margins(vc, 1); ++ scrollback_max -= count; ++ if (scrollback_max < 0) ++ scrollback_max = 0; ++ scrollback_current = 0; ++} ++ ++static __inline__ void ypan_down_redraw(struct vc_data *vc, int t, int count) ++{ ++ struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; ++ struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_display *p = &fb_display[vc->vc_num]; ++ ++ p->yscroll -= count; ++ ++ if (p->yscroll < 0) { ++ p->yscroll += p->vrows - vc->vc_rows; ++ fbcon_redraw_move(vc, p, t, vc->vc_rows - count, t + count); ++ } ++ ++ ops->var.xoffset = 0; ++ ops->var.yoffset = p->yscroll * vc->vc_font.height; ++ ops->var.vmode &= ~FB_VMODE_YWRAP; ++ ops->update_start(info); ++ fbcon_clear_margins(vc, 1); ++ scrollback_max -= count; ++ if (scrollback_max < 0) ++ scrollback_max = 0; ++ scrollback_current = 0; ++} ++ ++static void fbcon_redraw_move(struct vc_data *vc, struct fbcon_display *p, ++ int line, int count, int dy) ++{ ++ unsigned short *s = (unsigned short *) ++ (vc->vc_origin + vc->vc_size_row * line); ++ ++ while (count--) { ++ unsigned short *start = s; ++ unsigned short *le = advance_row(s, 1); ++ unsigned short c; ++ int x = 0; ++ unsigned short attr = 1; ++ ++ do { ++ c = scr_readw(s); ++ if (attr != (c & 0xff00)) { ++ attr = c & 0xff00; ++ if (s > start) { ++ fbcon_putcs(vc, start, s - start, ++ dy, x); ++ x += s - start; ++ start = s; ++ } ++ } ++ console_conditional_schedule(); ++ s++; ++ } while (s < le); ++ if (s > start) ++ fbcon_putcs(vc, start, s - start, dy, x); ++ console_conditional_schedule(); ++ dy++; ++ } ++} ++ ++static void fbcon_redraw_blit(struct vc_data *vc, struct fb_info *info, ++ struct fbcon_display *p, int line, int count, int ycount) ++{ ++ int offset = ycount * vc->vc_cols; ++ unsigned short *d = (unsigned short *) ++ (vc->vc_origin + vc->vc_size_row * line); ++ unsigned short *s = d + offset; ++ struct fbcon_ops *ops = info->fbcon_par; ++ ++ while (count--) { ++ unsigned short *start = s; ++ unsigned short *le = advance_row(s, 1); ++ unsigned short c; ++ int x = 0; ++ ++ do { ++ c = scr_readw(s); ++ ++ if (c == scr_readw(d)) { ++ if (s > start) { ++ ops->bmove(vc, info, line + ycount, x, ++ line, x, 1, s-start); ++ x += s - start + 1; ++ start = s + 1; ++ } else { ++ x++; ++ start++; ++ } ++ } ++ ++ scr_writew(c, d); ++ console_conditional_schedule(); ++ s++; ++ d++; ++ } while (s < le); ++ if (s > start) ++ ops->bmove(vc, info, line + ycount, x, line, x, 1, ++ s-start); ++ console_conditional_schedule(); ++ if (ycount > 0) ++ line++; ++ else { ++ line--; ++ /* NOTE: We subtract two lines from these pointers */ ++ s -= vc->vc_size_row; ++ d -= vc->vc_size_row; ++ } ++ } ++} ++ + static void fbcon_redraw(struct vc_data *vc, struct fbcon_display *p, + int line, int count, int offset) + { +@@ -1450,6 +1687,7 @@ + { + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; + struct fbcon_display *p = &fb_display[vc->vc_num]; ++ int scroll_partial = info->flags & FBINFO_PARTIAL_PAN_OK; + + if (fbcon_is_inactive(vc, info)) + return true; +@@ -1466,32 +1704,249 @@ + case SM_UP: + if (count > vc->vc_rows) /* Maximum realistic size */ + count = vc->vc_rows; ++ if (logo_shown >= 0) ++ goto redraw_up; ++ switch (p->scrollmode) { ++ case SCROLL_MOVE: ++ fbcon_redraw_blit(vc, info, p, t, b - t - count, ++ count); ++ fbcon_clear(vc, b - count, 0, count, vc->vc_cols); ++ scr_memsetw((unsigned short *) (vc->vc_origin + ++ vc->vc_size_row * ++ (b - count)), ++ vc->vc_video_erase_char, ++ vc->vc_size_row * count); ++ return true; ++ ++ case SCROLL_WRAP_MOVE: ++ if (b - t - count > 3 * vc->vc_rows >> 2) { ++ if (t > 0) ++ fbcon_bmove(vc, 0, 0, count, 0, t, ++ vc->vc_cols); ++ ywrap_up(vc, count); ++ if (vc->vc_rows - b > 0) ++ fbcon_bmove(vc, b - count, 0, b, 0, ++ vc->vc_rows - b, ++ vc->vc_cols); ++ } else if (info->flags & FBINFO_READS_FAST) ++ fbcon_bmove(vc, t + count, 0, t, 0, ++ b - t - count, vc->vc_cols); ++ else ++ goto redraw_up; ++ fbcon_clear(vc, b - count, 0, count, vc->vc_cols); ++ break; ++ ++ case SCROLL_PAN_REDRAW: ++ if ((p->yscroll + count <= ++ 2 * (p->vrows - vc->vc_rows)) ++ && ((!scroll_partial && (b - t == vc->vc_rows)) ++ || (scroll_partial ++ && (b - t - count > ++ 3 * vc->vc_rows >> 2)))) { ++ if (t > 0) ++ fbcon_redraw_move(vc, p, 0, t, count); ++ ypan_up_redraw(vc, t, count); ++ if (vc->vc_rows - b > 0) ++ fbcon_redraw_move(vc, p, b, ++ vc->vc_rows - b, b); ++ } else ++ fbcon_redraw_move(vc, p, t + count, b - t - count, t); ++ fbcon_clear(vc, b - count, 0, count, vc->vc_cols); ++ break; ++ ++ case SCROLL_PAN_MOVE: ++ if ((p->yscroll + count <= ++ 2 * (p->vrows - vc->vc_rows)) ++ && ((!scroll_partial && (b - t == vc->vc_rows)) ++ || (scroll_partial ++ && (b - t - count > ++ 3 * vc->vc_rows >> 2)))) { ++ if (t > 0) ++ fbcon_bmove(vc, 0, 0, count, 0, t, ++ vc->vc_cols); ++ ypan_up(vc, count); ++ if (vc->vc_rows - b > 0) ++ fbcon_bmove(vc, b - count, 0, b, 0, ++ vc->vc_rows - b, ++ vc->vc_cols); ++ } else if (info->flags & FBINFO_READS_FAST) ++ fbcon_bmove(vc, t + count, 0, t, 0, ++ b - t - count, vc->vc_cols); ++ else ++ goto redraw_up; ++ fbcon_clear(vc, b - count, 0, count, vc->vc_cols); ++ break; ++ ++ case SCROLL_REDRAW: ++ redraw_up: ++ fbcon_redraw(vc, p, t, b - t - count, ++ count * vc->vc_cols); ++ fbcon_clear(vc, b - count, 0, count, vc->vc_cols); ++ scr_memsetw((unsigned short *) (vc->vc_origin + ++ vc->vc_size_row * ++ (b - count)), ++ vc->vc_video_erase_char, ++ vc->vc_size_row * count); ++ return true; ++ } ++ break; +- fbcon_redraw(vc, p, t, b - t - count, +- count * vc->vc_cols); +- fbcon_clear(vc, b - count, 0, count, vc->vc_cols); +- scr_memsetw((unsigned short *) (vc->vc_origin + +- vc->vc_size_row * +- (b - count)), +- vc->vc_video_erase_char, +- vc->vc_size_row * count); +- return true; + + case SM_DOWN: + if (count > vc->vc_rows) /* Maximum realistic size */ + count = vc->vc_rows; ++ if (logo_shown >= 0) ++ goto redraw_down; ++ switch (p->scrollmode) { ++ case SCROLL_MOVE: ++ fbcon_redraw_blit(vc, info, p, b - 1, b - t - count, ++ -count); ++ fbcon_clear(vc, t, 0, count, vc->vc_cols); ++ scr_memsetw((unsigned short *) (vc->vc_origin + ++ vc->vc_size_row * ++ t), ++ vc->vc_video_erase_char, ++ vc->vc_size_row * count); ++ return true; ++ ++ case SCROLL_WRAP_MOVE: ++ if (b - t - count > 3 * vc->vc_rows >> 2) { ++ if (vc->vc_rows - b > 0) ++ fbcon_bmove(vc, b, 0, b - count, 0, ++ vc->vc_rows - b, ++ vc->vc_cols); ++ ywrap_down(vc, count); ++ if (t > 0) ++ fbcon_bmove(vc, count, 0, 0, 0, t, ++ vc->vc_cols); ++ } else if (info->flags & FBINFO_READS_FAST) ++ fbcon_bmove(vc, t, 0, t + count, 0, ++ b - t - count, vc->vc_cols); ++ else ++ goto redraw_down; ++ fbcon_clear(vc, t, 0, count, vc->vc_cols); ++ break; ++ ++ case SCROLL_PAN_MOVE: ++ if ((count - p->yscroll <= p->vrows - vc->vc_rows) ++ && ((!scroll_partial && (b - t == vc->vc_rows)) ++ || (scroll_partial ++ && (b - t - count > ++ 3 * vc->vc_rows >> 2)))) { ++ if (vc->vc_rows - b > 0) ++ fbcon_bmove(vc, b, 0, b - count, 0, ++ vc->vc_rows - b, ++ vc->vc_cols); ++ ypan_down(vc, count); ++ if (t > 0) ++ fbcon_bmove(vc, count, 0, 0, 0, t, ++ vc->vc_cols); ++ } else if (info->flags & FBINFO_READS_FAST) ++ fbcon_bmove(vc, t, 0, t + count, 0, ++ b - t - count, vc->vc_cols); ++ else ++ goto redraw_down; ++ fbcon_clear(vc, t, 0, count, vc->vc_cols); ++ break; ++ ++ case SCROLL_PAN_REDRAW: ++ if ((count - p->yscroll <= p->vrows - vc->vc_rows) ++ && ((!scroll_partial && (b - t == vc->vc_rows)) ++ || (scroll_partial ++ && (b - t - count > ++ 3 * vc->vc_rows >> 2)))) { ++ if (vc->vc_rows - b > 0) ++ fbcon_redraw_move(vc, p, b, vc->vc_rows - b, ++ b - count); ++ ypan_down_redraw(vc, t, count); ++ if (t > 0) ++ fbcon_redraw_move(vc, p, count, t, 0); ++ } else ++ fbcon_redraw_move(vc, p, t, b - t - count, t + count); ++ fbcon_clear(vc, t, 0, count, vc->vc_cols); ++ break; ++ ++ case SCROLL_REDRAW: ++ redraw_down: ++ fbcon_redraw(vc, p, b - 1, b - t - count, ++ -count * vc->vc_cols); ++ fbcon_clear(vc, t, 0, count, vc->vc_cols); ++ scr_memsetw((unsigned short *) (vc->vc_origin + ++ vc->vc_size_row * ++ t), ++ vc->vc_video_erase_char, ++ vc->vc_size_row * count); ++ return true; ++ } +- fbcon_redraw(vc, p, b - 1, b - t - count, +- -count * vc->vc_cols); +- fbcon_clear(vc, t, 0, count, vc->vc_cols); +- scr_memsetw((unsigned short *) (vc->vc_origin + +- vc->vc_size_row * +- t), +- vc->vc_video_erase_char, +- vc->vc_size_row * count); +- return true; + } + return false; + } + ++ ++static void fbcon_bmove(struct vc_data *vc, int sy, int sx, int dy, int dx, ++ int height, int width) ++{ ++ struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; ++ struct fbcon_display *p = &fb_display[vc->vc_num]; ++ ++ if (fbcon_is_inactive(vc, info)) ++ return; ++ ++ if (!width || !height) ++ return; ++ ++ /* Split blits that cross physical y_wrap case. ++ * Pathological case involves 4 blits, better to use recursive ++ * code rather than unrolled case ++ * ++ * Recursive invocations don't need to erase the cursor over and ++ * over again, so we use fbcon_bmove_rec() ++ */ ++ fbcon_bmove_rec(vc, p, sy, sx, dy, dx, height, width, ++ p->vrows - p->yscroll); ++} ++ ++static void fbcon_bmove_rec(struct vc_data *vc, struct fbcon_display *p, int sy, int sx, ++ int dy, int dx, int height, int width, u_int y_break) ++{ ++ struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; ++ struct fbcon_ops *ops = info->fbcon_par; ++ u_int b; ++ ++ if (sy < y_break && sy + height > y_break) { ++ b = y_break - sy; ++ if (dy < sy) { /* Avoid trashing self */ ++ fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width, ++ y_break); ++ fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx, ++ height - b, width, y_break); ++ } else { ++ fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx, ++ height - b, width, y_break); ++ fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width, ++ y_break); ++ } ++ return; ++ } ++ ++ if (dy < y_break && dy + height > y_break) { ++ b = y_break - dy; ++ if (dy < sy) { /* Avoid trashing self */ ++ fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width, ++ y_break); ++ fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx, ++ height - b, width, y_break); ++ } else { ++ fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx, ++ height - b, width, y_break); ++ fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width, ++ y_break); ++ } ++ return; ++ } ++ ops->bmove(vc, info, real_y(p, sy), sx, real_y(p, dy), dx, ++ height, width); ++} ++ + static void updatescrollmode(struct fbcon_display *p, + struct fb_info *info, + struct vc_data *vc) +@@ -1664,7 +2119,21 @@ + + updatescrollmode(p, info, vc); + ++ switch (p->scrollmode) { ++ case SCROLL_WRAP_MOVE: ++ scrollback_phys_max = p->vrows - vc->vc_rows; ++ break; ++ case SCROLL_PAN_MOVE: ++ case SCROLL_PAN_REDRAW: ++ scrollback_phys_max = p->vrows - 2 * vc->vc_rows; ++ if (scrollback_phys_max < 0) ++ scrollback_phys_max = 0; ++ break; ++ default: ++ scrollback_phys_max = 0; ++ break; ++ } ++ +- scrollback_phys_max = 0; + scrollback_max = 0; + scrollback_current = 0; + +--- b/drivers/video/fbdev/core/fbcon.h ++++ a/drivers/video/fbdev/core/fbcon.h +@@ -29,6 +29,7 @@ + /* Filled in by the low-level console driver */ + const u_char *fontdata; + int userfont; /* != 0 if fontdata kmalloc()ed */ ++ u_short scrollmode; /* Scroll Method */ + u_short inverse; /* != 0 text black on white as default */ + short yscroll; /* Hardware scrolling */ + int vrows; /* number of virtual rows */ +@@ -51,6 +52,8 @@ + }; + + struct fbcon_ops { ++ void (*bmove)(struct vc_data *vc, struct fb_info *info, int sy, ++ int sx, int dy, int dx, int height, int width); + void (*clear)(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int height, int width); + void (*putcs)(struct vc_data *vc, struct fb_info *info, +@@ -149,6 +152,62 @@ + #define attr_bgcol_ec(bgshift, vc, info) attr_col_ec(bgshift, vc, info, 0) + #define attr_fgcol_ec(fgshift, vc, info) attr_col_ec(fgshift, vc, info, 1) + ++ /* ++ * Scroll Method ++ */ ++ ++/* There are several methods fbcon can use to move text around the screen: ++ * ++ * Operation Pan Wrap ++ *--------------------------------------------- ++ * SCROLL_MOVE copyarea No No ++ * SCROLL_PAN_MOVE copyarea Yes No ++ * SCROLL_WRAP_MOVE copyarea No Yes ++ * SCROLL_REDRAW imageblit No No ++ * SCROLL_PAN_REDRAW imageblit Yes No ++ * SCROLL_WRAP_REDRAW imageblit No Yes ++ * ++ * (SCROLL_WRAP_REDRAW is not implemented yet) ++ * ++ * In general, fbcon will choose the best scrolling ++ * method based on the rule below: ++ * ++ * Pan/Wrap > accel imageblit > accel copyarea > ++ * soft imageblit > (soft copyarea) ++ * ++ * Exception to the rule: Pan + accel copyarea is ++ * preferred over Pan + accel imageblit. ++ * ++ * The above is typical for PCI/AGP cards. Unless ++ * overridden, fbcon will never use soft copyarea. ++ * ++ * If you need to override the above rule, set the ++ * appropriate flags in fb_info->flags. For example, ++ * to prefer copyarea over imageblit, set ++ * FBINFO_READS_FAST. ++ * ++ * Other notes: ++ * + use the hardware engine to move the text ++ * (hw-accelerated copyarea() and fillrect()) ++ * + use hardware-supported panning on a large virtual screen ++ * + amifb can not only pan, but also wrap the display by N lines ++ * (i.e. visible line i = physical line (i+N) % yres). ++ * + read what's already rendered on the screen and ++ * write it in a different place (this is cfb_copyarea()) ++ * + re-render the text to the screen ++ * ++ * Whether to use wrapping or panning can only be figured out at ++ * runtime (when we know whether our font height is a multiple ++ * of the pan/wrap step) ++ * ++ */ ++ ++#define SCROLL_MOVE 0x001 ++#define SCROLL_PAN_MOVE 0x002 ++#define SCROLL_WRAP_MOVE 0x003 ++#define SCROLL_REDRAW 0x004 ++#define SCROLL_PAN_REDRAW 0x005 ++ + #ifdef CONFIG_FB_TILEBLITTING + extern void fbcon_set_tileops(struct vc_data *vc, struct fb_info *info); + #endif +--- b/drivers/video/fbdev/core/fbcon_ccw.c ++++ a/drivers/video/fbdev/core/fbcon_ccw.c +@@ -59,12 +59,31 @@ + } + } + ++ ++static void ccw_bmove(struct vc_data *vc, struct fb_info *info, int sy, ++ int sx, int dy, int dx, int height, int width) ++{ ++ struct fbcon_ops *ops = info->fbcon_par; ++ struct fb_copyarea area; ++ u32 vyres = GETVYRES(ops->p->scrollmode, info); ++ ++ area.sx = sy * vc->vc_font.height; ++ area.sy = vyres - ((sx + width) * vc->vc_font.width); ++ area.dx = dy * vc->vc_font.height; ++ area.dy = vyres - ((dx + width) * vc->vc_font.width); ++ area.width = height * vc->vc_font.height; ++ area.height = width * vc->vc_font.width; ++ ++ info->fbops->fb_copyarea(info, &area); ++} ++ + static void ccw_clear(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int height, int width) + { ++ struct fbcon_ops *ops = info->fbcon_par; + struct fb_fillrect region; + int bgshift = (vc->vc_hi_font_mask) ? 13 : 12; ++ u32 vyres = GETVYRES(ops->p->scrollmode, info); +- u32 vyres = info->var.yres; + + region.color = attr_bgcol_ec(bgshift,vc,info); + region.dx = sy * vc->vc_font.height; +@@ -121,7 +140,7 @@ + u32 cnt, pitch, size; + u32 attribute = get_attribute(info, scr_readw(s)); + u8 *dst, *buf = NULL; ++ u32 vyres = GETVYRES(ops->p->scrollmode, info); +- u32 vyres = info->var.yres; + + if (!ops->fontbuffer) + return; +@@ -210,7 +229,7 @@ + int attribute, use_sw = vc->vc_cursor_type & CUR_SW; + int err = 1, dx, dy; + char *src; ++ u32 vyres = GETVYRES(ops->p->scrollmode, info); +- u32 vyres = info->var.yres; + + if (!ops->fontbuffer) + return; +@@ -368,7 +387,7 @@ + { + struct fbcon_ops *ops = info->fbcon_par; + u32 yoffset; ++ u32 vyres = GETVYRES(ops->p->scrollmode, info); +- u32 vyres = info->var.yres; + int err; + + yoffset = (vyres - info->var.yres) - ops->var.xoffset; +@@ -383,6 +402,7 @@ + + void fbcon_rotate_ccw(struct fbcon_ops *ops) + { ++ ops->bmove = ccw_bmove; + ops->clear = ccw_clear; + ops->putcs = ccw_putcs; + ops->clear_margins = ccw_clear_margins; +--- b/drivers/video/fbdev/core/fbcon_cw.c ++++ a/drivers/video/fbdev/core/fbcon_cw.c +@@ -44,12 +44,31 @@ + } + } + ++ ++static void cw_bmove(struct vc_data *vc, struct fb_info *info, int sy, ++ int sx, int dy, int dx, int height, int width) ++{ ++ struct fbcon_ops *ops = info->fbcon_par; ++ struct fb_copyarea area; ++ u32 vxres = GETVXRES(ops->p->scrollmode, info); ++ ++ area.sx = vxres - ((sy + height) * vc->vc_font.height); ++ area.sy = sx * vc->vc_font.width; ++ area.dx = vxres - ((dy + height) * vc->vc_font.height); ++ area.dy = dx * vc->vc_font.width; ++ area.width = height * vc->vc_font.height; ++ area.height = width * vc->vc_font.width; ++ ++ info->fbops->fb_copyarea(info, &area); ++} ++ + static void cw_clear(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int height, int width) + { ++ struct fbcon_ops *ops = info->fbcon_par; + struct fb_fillrect region; + int bgshift = (vc->vc_hi_font_mask) ? 13 : 12; ++ u32 vxres = GETVXRES(ops->p->scrollmode, info); +- u32 vxres = info->var.xres; + + region.color = attr_bgcol_ec(bgshift,vc,info); + region.dx = vxres - ((sy + height) * vc->vc_font.height); +@@ -106,7 +125,7 @@ + u32 cnt, pitch, size; + u32 attribute = get_attribute(info, scr_readw(s)); + u8 *dst, *buf = NULL; ++ u32 vxres = GETVXRES(ops->p->scrollmode, info); +- u32 vxres = info->var.xres; + + if (!ops->fontbuffer) + return; +@@ -193,7 +212,7 @@ + int attribute, use_sw = vc->vc_cursor_type & CUR_SW; + int err = 1, dx, dy; + char *src; ++ u32 vxres = GETVXRES(ops->p->scrollmode, info); +- u32 vxres = info->var.xres; + + if (!ops->fontbuffer) + return; +@@ -350,7 +369,7 @@ + static int cw_update_start(struct fb_info *info) + { + struct fbcon_ops *ops = info->fbcon_par; ++ u32 vxres = GETVXRES(ops->p->scrollmode, info); +- u32 vxres = info->var.xres; + u32 xoffset; + int err; + +@@ -366,6 +385,7 @@ + + void fbcon_rotate_cw(struct fbcon_ops *ops) + { ++ ops->bmove = cw_bmove; + ops->clear = cw_clear; + ops->putcs = cw_putcs; + ops->clear_margins = cw_clear_margins; +--- b/drivers/video/fbdev/core/fbcon_rotate.h ++++ a/drivers/video/fbdev/core/fbcon_rotate.h +@@ -11,6 +11,15 @@ + #ifndef _FBCON_ROTATE_H + #define _FBCON_ROTATE_H + ++#define GETVYRES(s,i) ({ \ ++ (s == SCROLL_REDRAW || s == SCROLL_MOVE) ? \ ++ (i)->var.yres : (i)->var.yres_virtual; }) ++ ++#define GETVXRES(s,i) ({ \ ++ (s == SCROLL_REDRAW || s == SCROLL_MOVE || !(i)->fix.xpanstep) ? \ ++ (i)->var.xres : (i)->var.xres_virtual; }) ++ ++ + static inline int pattern_test_bit(u32 x, u32 y, u32 pitch, const char *pat) + { + u32 tmp = (y * pitch) + x, index = tmp / 8, bit = tmp % 8; +--- b/drivers/video/fbdev/core/fbcon_ud.c ++++ a/drivers/video/fbdev/core/fbcon_ud.c +@@ -44,13 +44,33 @@ + } + } + ++ ++static void ud_bmove(struct vc_data *vc, struct fb_info *info, int sy, ++ int sx, int dy, int dx, int height, int width) ++{ ++ struct fbcon_ops *ops = info->fbcon_par; ++ struct fb_copyarea area; ++ u32 vyres = GETVYRES(ops->p->scrollmode, info); ++ u32 vxres = GETVXRES(ops->p->scrollmode, info); ++ ++ area.sy = vyres - ((sy + height) * vc->vc_font.height); ++ area.sx = vxres - ((sx + width) * vc->vc_font.width); ++ area.dy = vyres - ((dy + height) * vc->vc_font.height); ++ area.dx = vxres - ((dx + width) * vc->vc_font.width); ++ area.height = height * vc->vc_font.height; ++ area.width = width * vc->vc_font.width; ++ ++ info->fbops->fb_copyarea(info, &area); ++} ++ + static void ud_clear(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int height, int width) + { ++ struct fbcon_ops *ops = info->fbcon_par; + struct fb_fillrect region; + int bgshift = (vc->vc_hi_font_mask) ? 13 : 12; ++ u32 vyres = GETVYRES(ops->p->scrollmode, info); ++ u32 vxres = GETVXRES(ops->p->scrollmode, info); +- u32 vyres = info->var.yres; +- u32 vxres = info->var.xres; + + region.color = attr_bgcol_ec(bgshift,vc,info); + region.dy = vyres - ((sy + height) * vc->vc_font.height); +@@ -142,8 +162,8 @@ + u32 mod = vc->vc_font.width % 8, cnt, pitch, size; + u32 attribute = get_attribute(info, scr_readw(s)); + u8 *dst, *buf = NULL; ++ u32 vyres = GETVYRES(ops->p->scrollmode, info); ++ u32 vxres = GETVXRES(ops->p->scrollmode, info); +- u32 vyres = info->var.yres; +- u32 vxres = info->var.xres; + + if (!ops->fontbuffer) + return; +@@ -239,8 +259,8 @@ + int attribute, use_sw = vc->vc_cursor_type & CUR_SW; + int err = 1, dx, dy; + char *src; ++ u32 vyres = GETVYRES(ops->p->scrollmode, info); ++ u32 vxres = GETVXRES(ops->p->scrollmode, info); +- u32 vyres = info->var.yres; +- u32 vxres = info->var.xres; + + if (!ops->fontbuffer) + return; +@@ -390,8 +410,8 @@ + { + struct fbcon_ops *ops = info->fbcon_par; + int xoffset, yoffset; ++ u32 vyres = GETVYRES(ops->p->scrollmode, info); ++ u32 vxres = GETVXRES(ops->p->scrollmode, info); +- u32 vyres = info->var.yres; +- u32 vxres = info->var.xres; + int err; + + xoffset = vxres - info->var.xres - ops->var.xoffset; +@@ -409,6 +429,7 @@ + + void fbcon_rotate_ud(struct fbcon_ops *ops) + { ++ ops->bmove = ud_bmove; + ops->clear = ud_clear; + ops->putcs = ud_putcs; + ops->clear_margins = ud_clear_margins; +--- b/drivers/video/fbdev/core/tileblit.c ++++ a/drivers/video/fbdev/core/tileblit.c +@@ -16,6 +16,21 @@ + #include + #include "fbcon.h" + ++static void tile_bmove(struct vc_data *vc, struct fb_info *info, int sy, ++ int sx, int dy, int dx, int height, int width) ++{ ++ struct fb_tilearea area; ++ ++ area.sx = sx; ++ area.sy = sy; ++ area.dx = dx; ++ area.dy = dy; ++ area.height = height; ++ area.width = width; ++ ++ info->tileops->fb_tilecopy(info, &area); ++} ++ + static void tile_clear(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int height, int width) + { +@@ -118,6 +133,7 @@ + struct fb_tilemap map; + struct fbcon_ops *ops = info->fbcon_par; + ++ ops->bmove = tile_bmove; + ops->clear = tile_clear; + ops->putcs = tile_putcs; + ops->clear_margins = tile_clear_margins; +--- b/drivers/video/fbdev/skeletonfb.c ++++ a/drivers/video/fbdev/skeletonfb.c +@@ -505,15 +505,15 @@ + } + + /** ++ * xxxfb_copyarea - REQUIRED function. Can use generic routines if ++ * non acclerated hardware and packed pixel based. +- * xxxfb_copyarea - OBSOLETE function. + * Copies one area of the screen to another area. +- * Will be deleted in a future version + * + * @info: frame buffer structure that represents a single frame buffer + * @area: Structure providing the data to copy the framebuffer contents + * from one region to another. + * ++ * This drawing operation copies a rectangular area from one area of the +- * This drawing operation copied a rectangular area from one area of the + * screen to another area. + */ + void xxxfb_copyarea(struct fb_info *p, const struct fb_copyarea *area) +@@ -645,9 +645,9 @@ + .fb_setcolreg = xxxfb_setcolreg, + .fb_blank = xxxfb_blank, + .fb_pan_display = xxxfb_pan_display, ++ .fb_fillrect = xxxfb_fillrect, /* Needed !!! */ ++ .fb_copyarea = xxxfb_copyarea, /* Needed !!! */ ++ .fb_imageblit = xxxfb_imageblit, /* Needed !!! */ +- .fb_fillrect = xxxfb_fillrect, /* Needed !!! */ +- .fb_copyarea = xxxfb_copyarea, /* Obsolete */ +- .fb_imageblit = xxxfb_imageblit, /* Needed !!! */ + .fb_cursor = xxxfb_cursor, /* Optional !!! */ + .fb_sync = xxxfb_sync, + .fb_ioctl = xxxfb_ioctl, +--- b/include/linux/fb.h ++++ a/include/linux/fb.h +@@ -262,7 +262,7 @@ + + /* Draws a rectangle */ + void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect); ++ /* Copy data from area to another */ +- /* Copy data from area to another. Obsolete. */ + void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region); + /* Draws a image to the display */ + void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image); diff --git a/recipes-kernel/linux/linux-pinephonepro/0002-bootsplash.patch b/recipes-kernel/linux/linux-pinephonepro/0002-bootsplash.patch new file mode 100644 index 0000000..92d62ca --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0002-bootsplash.patch @@ -0,0 +1,669 @@ +diff --git a/MAINTAINERS b/MAINTAINERS +index b5633b56391e..5c237445761e 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -2712,6 +2712,7 @@ S: Maintained + F: drivers/video/fbdev/core/bootsplash*.* + F: drivers/video/fbdev/core/dummycon.c + F: include/linux/bootsplash.h ++F: include/uapi/linux/bootsplash_file.h + + BPF (Safe dynamic programs and tools) + M: Alexei Starovoitov +diff --git a/drivers/video/fbdev/core/Makefile b/drivers/video/fbdev/core/Makefile +index 66895321928e..6a8d1bab8a01 100644 +--- a/drivers/video/fbdev/core/Makefile ++++ b/drivers/video/fbdev/core/Makefile +@@ -31,4 +31,4 @@ obj-$(CONFIG_FB_SVGALIB) += svgalib.o + obj-$(CONFIG_FB_DDC) += fb_ddc.o + + obj-$(CONFIG_BOOTSPLASH) += bootsplash.o bootsplash_render.o \ +- dummyblit.o ++ bootsplash_load.o dummyblit.o +diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c +index e449755af268..843c5400fefc 100644 +--- a/drivers/video/fbdev/core/bootsplash.c ++++ b/drivers/video/fbdev/core/bootsplash.c +@@ -32,6 +32,7 @@ + #include + + #include "bootsplash_internal.h" ++#include "uapi/linux/bootsplash_file.h" + + + /* +@@ -102,10 +103,17 @@ static bool is_fb_compatible(const struct fb_info *info) + */ + void bootsplash_render_full(struct fb_info *info) + { ++ mutex_lock(&splash_state.data_lock); ++ + if (!is_fb_compatible(info)) +- return; ++ goto out; ++ ++ bootsplash_do_render_background(info, splash_state.file); ++ ++ bootsplash_do_render_pictures(info, splash_state.file); + +- bootsplash_do_render_background(info); ++out: ++ mutex_unlock(&splash_state.data_lock); + } + + +@@ -116,6 +124,7 @@ bool bootsplash_would_render_now(void) + { + return !oops_in_progress + && !console_blanked ++ && splash_state.file + && bootsplash_is_enabled(); + } + +@@ -252,6 +261,7 @@ static struct platform_driver splash_driver = { + void bootsplash_init(void) + { + int ret; ++ struct splash_file_priv *fp; + + /* Initialized already? */ + if (splash_state.splash_device) +@@ -280,8 +290,26 @@ void bootsplash_init(void) + } + + ++ mutex_init(&splash_state.data_lock); ++ set_bit(0, &splash_state.enabled); ++ + INIT_WORK(&splash_state.work_redraw_vc, splash_callback_redraw_vc); + ++ ++ if (!splash_state.bootfile || !strlen(splash_state.bootfile)) ++ return; ++ ++ fp = bootsplash_load_firmware(&splash_state.splash_device->dev, ++ splash_state.bootfile); ++ ++ if (!fp) ++ goto err; ++ ++ mutex_lock(&splash_state.data_lock); ++ splash_state.splash_fb = NULL; ++ splash_state.file = fp; ++ mutex_unlock(&splash_state.data_lock); ++ + return; + + err_device: +@@ -292,3 +320,7 @@ void bootsplash_init(void) + err: + pr_err("Failed to initialize.\n"); + } ++ ++ ++module_param_named(bootfile, splash_state.bootfile, charp, 0444); ++MODULE_PARM_DESC(bootfile, "Bootsplash file to load on boot"); +diff --git a/drivers/video/fbdev/core/bootsplash_internal.h b/drivers/video/fbdev/core/bootsplash_internal.h +index b11da5cb90bf..71e2a27ac0b8 100644 +--- a/drivers/video/fbdev/core/bootsplash_internal.h ++++ b/drivers/video/fbdev/core/bootsplash_internal.h +@@ -15,15 +15,43 @@ + + #include + #include ++#include + #include + #include + #include + ++#include "uapi/linux/bootsplash_file.h" ++ + + /* + * Runtime types + */ ++struct splash_blob_priv { ++ struct splash_blob_header *blob_header; ++ const void *data; ++}; ++ ++ ++struct splash_pic_priv { ++ const struct splash_pic_header *pic_header; ++ ++ struct splash_blob_priv *blobs; ++ u16 blobs_loaded; ++}; ++ ++ ++struct splash_file_priv { ++ const struct firmware *fw; ++ const struct splash_file_header *header; ++ ++ struct splash_pic_priv *pics; ++}; ++ ++ + struct splash_priv { ++ /* Bootup and runtime state */ ++ char *bootfile; ++ + /* + * Enabled/disabled state, to be used with atomic bit operations. + * Bit 0: 0 = Splash hidden +@@ -43,6 +71,13 @@ struct splash_priv { + struct platform_device *splash_device; + + struct work_struct work_redraw_vc; ++ ++ /* Splash data structures including lock for everything below */ ++ struct mutex data_lock; ++ ++ struct fb_info *splash_fb; ++ ++ struct splash_file_priv *file; + }; + + +@@ -50,6 +85,14 @@ struct splash_priv { + /* + * Rendering functions + */ +-void bootsplash_do_render_background(struct fb_info *info); ++void bootsplash_do_render_background(struct fb_info *info, ++ const struct splash_file_priv *fp); ++void bootsplash_do_render_pictures(struct fb_info *info, ++ const struct splash_file_priv *fp); ++ ++ ++void bootsplash_free_file(struct splash_file_priv *fp); ++struct splash_file_priv *bootsplash_load_firmware(struct device *device, ++ const char *path); + + #endif +diff --git a/drivers/video/fbdev/core/bootsplash_load.c b/drivers/video/fbdev/core/bootsplash_load.c +new file mode 100644 +index 000000000000..fd807571ab7d +--- /dev/null ++++ b/drivers/video/fbdev/core/bootsplash_load.c +@@ -0,0 +1,225 @@ ++/* ++ * Kernel based bootsplash. ++ * ++ * (Loading and freeing functions) ++ * ++ * Authors: ++ * Max Staudt ++ * ++ * SPDX-License-Identifier: GPL-2.0 ++ */ ++ ++#define pr_fmt(fmt) "bootsplash: " fmt ++ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "bootsplash_internal.h" ++#include "uapi/linux/bootsplash_file.h" ++ ++ ++ ++ ++/* ++ * Free all vmalloc()'d resources describing a splash file. ++ */ ++void bootsplash_free_file(struct splash_file_priv *fp) ++{ ++ if (!fp) ++ return; ++ ++ if (fp->pics) { ++ unsigned int i; ++ ++ for (i = 0; i < fp->header->num_pics; i++) { ++ struct splash_pic_priv *pp = &fp->pics[i]; ++ ++ if (pp->blobs) ++ vfree(pp->blobs); ++ } ++ ++ vfree(fp->pics); ++ } ++ ++ release_firmware(fp->fw); ++ vfree(fp); ++} ++ ++ ++ ++ ++/* ++ * Load a splash screen from a "firmware" file. ++ * ++ * Parsing, and sanity checks. ++ */ ++#ifdef __BIG_ENDIAN ++ #define BOOTSPLASH_MAGIC BOOTSPLASH_MAGIC_BE ++#else ++ #define BOOTSPLASH_MAGIC BOOTSPLASH_MAGIC_LE ++#endif ++ ++struct splash_file_priv *bootsplash_load_firmware(struct device *device, ++ const char *path) ++{ ++ const struct firmware *fw; ++ struct splash_file_priv *fp; ++ unsigned int i; ++ const u8 *walker; ++ ++ if (request_firmware(&fw, path, device)) ++ return NULL; ++ ++ if (fw->size < sizeof(struct splash_file_header) ++ || memcmp(fw->data, BOOTSPLASH_MAGIC, sizeof(fp->header->id))) { ++ pr_err("Not a bootsplash file.\n"); ++ ++ release_firmware(fw); ++ return NULL; ++ } ++ ++ fp = vzalloc(sizeof(struct splash_file_priv)); ++ if (!fp) { ++ release_firmware(fw); ++ return NULL; ++ } ++ ++ pr_info("Loading splash file (%li bytes)\n", fw->size); ++ ++ fp->fw = fw; ++ fp->header = (struct splash_file_header *)fw->data; ++ ++ /* Sanity checks */ ++ if (fp->header->version != BOOTSPLASH_VERSION) { ++ pr_err("Loaded v%d file, but we only support version %d\n", ++ fp->header->version, ++ BOOTSPLASH_VERSION); ++ ++ goto err; ++ } ++ ++ if (fw->size < sizeof(struct splash_file_header) ++ + fp->header->num_pics ++ * sizeof(struct splash_pic_header) ++ + fp->header->num_blobs ++ * sizeof(struct splash_blob_header)) { ++ pr_err("File incomplete.\n"); ++ ++ goto err; ++ } ++ ++ /* Read picture headers */ ++ if (fp->header->num_pics) { ++ fp->pics = vzalloc(fp->header->num_pics ++ * sizeof(struct splash_pic_priv)); ++ if (!fp->pics) ++ goto err; ++ } ++ ++ walker = fw->data + sizeof(struct splash_file_header); ++ for (i = 0; i < fp->header->num_pics; i++) { ++ struct splash_pic_priv *pp = &fp->pics[i]; ++ struct splash_pic_header *ph = (void *)walker; ++ ++ pr_debug("Picture %u: Size %ux%u\n", i, ph->width, ph->height); ++ ++ if (ph->num_blobs < 1) { ++ pr_err("Picture %u: Zero blobs? Aborting load.\n", i); ++ goto err; ++ } ++ ++ pp->pic_header = ph; ++ pp->blobs = vzalloc(ph->num_blobs ++ * sizeof(struct splash_blob_priv)); ++ if (!pp->blobs) ++ goto err; ++ ++ walker += sizeof(struct splash_pic_header); ++ } ++ ++ /* Read blob headers */ ++ for (i = 0; i < fp->header->num_blobs; i++) { ++ struct splash_blob_header *bh = (void *)walker; ++ struct splash_pic_priv *pp; ++ ++ if (walker + sizeof(struct splash_blob_header) ++ > fw->data + fw->size) ++ goto err; ++ ++ walker += sizeof(struct splash_blob_header); ++ ++ if (walker + bh->length > fw->data + fw->size) ++ goto err; ++ ++ if (bh->picture_id >= fp->header->num_pics) ++ goto nextblob; ++ ++ pp = &fp->pics[bh->picture_id]; ++ ++ pr_debug("Blob %u, pic %u, blobs_loaded %u, num_blobs %u.\n", ++ i, bh->picture_id, ++ pp->blobs_loaded, pp->pic_header->num_blobs); ++ ++ if (pp->blobs_loaded >= pp->pic_header->num_blobs) ++ goto nextblob; ++ ++ switch (bh->type) { ++ case 0: ++ /* Raw 24-bit packed pixels */ ++ if (bh->length != pp->pic_header->width ++ * pp->pic_header->height * 3) { ++ pr_err("Blob %u, type 1: Length doesn't match picture.\n", ++ i); ++ ++ goto err; ++ } ++ break; ++ default: ++ pr_warn("Blob %u, unknown type %u.\n", i, bh->type); ++ goto nextblob; ++ } ++ ++ pp->blobs[pp->blobs_loaded].blob_header = bh; ++ pp->blobs[pp->blobs_loaded].data = walker; ++ pp->blobs_loaded++; ++ ++nextblob: ++ walker += bh->length; ++ if (bh->length % 16) ++ walker += 16 - (bh->length % 16); ++ } ++ ++ if (walker != fw->data + fw->size) ++ pr_warn("Trailing data in splash file.\n"); ++ ++ /* Walk over pictures and ensure all blob slots are filled */ ++ for (i = 0; i < fp->header->num_pics; i++) { ++ struct splash_pic_priv *pp = &fp->pics[i]; ++ ++ if (pp->blobs_loaded != pp->pic_header->num_blobs) { ++ pr_err("Picture %u doesn't have all blob slots filled.\n", ++ i); ++ ++ goto err; ++ } ++ } ++ ++ pr_info("Loaded (%ld bytes, %u pics, %u blobs).\n", ++ fw->size, ++ fp->header->num_pics, ++ fp->header->num_blobs); ++ ++ return fp; ++ ++ ++err: ++ bootsplash_free_file(fp); ++ return NULL; ++} +diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c +index 4d7e0117f653..2ae36949d0e3 100644 +--- a/drivers/video/fbdev/core/bootsplash_render.c ++++ b/drivers/video/fbdev/core/bootsplash_render.c +@@ -19,6 +19,7 @@ + #include + + #include "bootsplash_internal.h" ++#include "uapi/linux/bootsplash_file.h" + + + +@@ -70,16 +71,69 @@ static inline u32 pack_pixel(const struct fb_var_screeninfo *dst_var, + } + + +-void bootsplash_do_render_background(struct fb_info *info) ++/* ++ * Copy from source and blend into the destination picture. ++ * Currently assumes that the source picture is 24bpp. ++ * Currently assumes that the destination is <= 32bpp. ++ */ ++static int splash_convert_to_fb(u8 *dst, ++ const struct fb_var_screeninfo *dst_var, ++ unsigned int dst_stride, ++ unsigned int dst_xoff, ++ unsigned int dst_yoff, ++ const u8 *src, ++ unsigned int src_width, ++ unsigned int src_height) ++{ ++ unsigned int x, y; ++ unsigned int src_stride = 3 * src_width; /* Assume 24bpp packed */ ++ u32 dst_octpp = dst_var->bits_per_pixel / 8; ++ ++ dst_xoff += dst_var->xoffset; ++ dst_yoff += dst_var->yoffset; ++ ++ /* Copy with stride and pixel size adjustment */ ++ for (y = 0; ++ y < src_height && y + dst_yoff < dst_var->yres_virtual; ++ y++) { ++ const u8 *srcline = src + (y * src_stride); ++ u8 *dstline = dst + ((y + dst_yoff) * dst_stride) ++ + (dst_xoff * dst_octpp); ++ ++ for (x = 0; ++ x < src_width && x + dst_xoff < dst_var->xres_virtual; ++ x++) { ++ u8 red, green, blue; ++ u32 dstpix; ++ ++ /* Read pixel */ ++ red = *srcline++; ++ green = *srcline++; ++ blue = *srcline++; ++ ++ /* Write pixel */ ++ dstpix = pack_pixel(dst_var, red, green, blue); ++ memcpy(dstline, &dstpix, dst_octpp); ++ ++ dstline += dst_octpp; ++ } ++ } ++ ++ return 0; ++} ++ ++ ++void bootsplash_do_render_background(struct fb_info *info, ++ const struct splash_file_priv *fp) + { + unsigned int x, y; + u32 dstpix; + u32 dst_octpp = info->var.bits_per_pixel / 8; + + dstpix = pack_pixel(&info->var, +- 0, +- 0, +- 0); ++ fp->header->bg_red, ++ fp->header->bg_green, ++ fp->header->bg_blue); + + for (y = 0; y < info->var.yres_virtual; y++) { + u8 *dstline = info->screen_buffer + (y * info->fix.line_length); +@@ -91,3 +145,44 @@ void bootsplash_do_render_background(struct fb_info *info) + } + } + } ++ ++ ++void bootsplash_do_render_pictures(struct fb_info *info, ++ const struct splash_file_priv *fp) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < fp->header->num_pics; i++) { ++ struct splash_blob_priv *bp; ++ struct splash_pic_priv *pp = &fp->pics[i]; ++ long dst_xoff, dst_yoff; ++ ++ if (pp->blobs_loaded < 1) ++ continue; ++ ++ bp = &pp->blobs[0]; ++ ++ if (!bp || bp->blob_header->type != 0) ++ continue; ++ ++ dst_xoff = (info->var.xres - pp->pic_header->width) / 2; ++ dst_yoff = (info->var.yres - pp->pic_header->height) / 2; ++ ++ if (dst_xoff < 0 ++ || dst_yoff < 0 ++ || dst_xoff + pp->pic_header->width > info->var.xres ++ || dst_yoff + pp->pic_header->height > info->var.yres) { ++ pr_info_once("Picture %u is out of bounds at current resolution: %dx%d\n" ++ "(this will only be printed once every reboot)\n", ++ i, info->var.xres, info->var.yres); ++ ++ continue; ++ } ++ ++ /* Draw next splash frame */ ++ splash_convert_to_fb(info->screen_buffer, &info->var, ++ info->fix.line_length, dst_xoff, dst_yoff, ++ bp->data, ++ pp->pic_header->width, pp->pic_header->height); ++ } ++} +diff --git a/include/uapi/linux/bootsplash_file.h b/include/uapi/linux/bootsplash_file.h +new file mode 100644 +index 000000000000..89dc9cca8f0c +--- /dev/null ++++ b/include/uapi/linux/bootsplash_file.h +@@ -0,0 +1,118 @@ ++/* ++ * Kernel based bootsplash. ++ * ++ * (File format) ++ * ++ * Authors: ++ * Max Staudt ++ * ++ * SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note ++ */ ++ ++#ifndef __BOOTSPLASH_FILE_H ++#define __BOOTSPLASH_FILE_H ++ ++ ++#define BOOTSPLASH_VERSION 55561 ++ ++ ++#include ++#include ++ ++ ++/* ++ * On-disk types ++ * ++ * A splash file consists of: ++ * - One single 'struct splash_file_header' ++ * - An array of 'struct splash_pic_header' ++ * - An array of raw data blocks, each padded to 16 bytes and ++ * preceded by a 'struct splash_blob_header' ++ * ++ * A single-frame splash may look like this: ++ * ++ * +--------------------+ ++ * | | ++ * | splash_file_header | ++ * | -> num_blobs = 1 | ++ * | -> num_pics = 1 | ++ * | | ++ * +--------------------+ ++ * | | ++ * | splash_pic_header | ++ * | | ++ * +--------------------+ ++ * | | ++ * | splash_blob_header | ++ * | -> type = 0 | ++ * | -> picture_id = 0 | ++ * | | ++ * | (raw RGB data) | ++ * | (pad to 16 bytes) | ++ * | | ++ * +--------------------+ ++ * ++ * All multi-byte values are stored on disk in the native format ++ * expected by the system the file will be used on. ++ */ ++#define BOOTSPLASH_MAGIC_BE "Linux bootsplash" ++#define BOOTSPLASH_MAGIC_LE "hsalpstoob xuniL" ++ ++struct splash_file_header { ++ uint8_t id[16]; /* "Linux bootsplash" (no trailing NUL) */ ++ ++ /* Splash file format version to avoid clashes */ ++ uint16_t version; ++ ++ /* The background color */ ++ uint8_t bg_red; ++ uint8_t bg_green; ++ uint8_t bg_blue; ++ uint8_t bg_reserved; ++ ++ /* ++ * Number of pic/blobs so we can allocate memory for internal ++ * structures ahead of time when reading the file ++ */ ++ uint16_t num_blobs; ++ uint8_t num_pics; ++ ++ uint8_t padding[103]; ++} __attribute__((__packed__)); ++ ++ ++struct splash_pic_header { ++ uint16_t width; ++ uint16_t height; ++ ++ /* ++ * Number of data packages associated with this picture. ++ * Currently, the only use for more than 1 is for animations. ++ */ ++ uint8_t num_blobs; ++ ++ uint8_t padding[27]; ++} __attribute__((__packed__)); ++ ++ ++struct splash_blob_header { ++ /* Length of the data block in bytes. */ ++ uint32_t length; ++ ++ /* ++ * Type of the contents. ++ * 0 - Raw RGB data. ++ */ ++ uint16_t type; ++ ++ /* ++ * Picture this blob is associated with. ++ * Blobs will be added to a picture in the order they are ++ * found in the file. ++ */ ++ uint8_t picture_id; ++ ++ uint8_t padding[9]; ++} __attribute__((__packed__)); ++ ++#endif diff --git a/recipes-kernel/linux/linux-pinephonepro/0002-revert-fbcon-remove-now-unusued-softback_lines-cursor-argument.patch b/recipes-kernel/linux/linux-pinephonepro/0002-revert-fbcon-remove-now-unusued-softback_lines-cursor-argument.patch new file mode 100644 index 0000000..e7d4da5 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0002-revert-fbcon-remove-now-unusued-softback_lines-cursor-argument.patch @@ -0,0 +1,150 @@ +--- b/drivers/video/fbdev/core/bitblit.c ++++ a/drivers/video/fbdev/core/bitblit.c +@@ -234,7 +234,7 @@ + } + + static void bit_cursor(struct vc_data *vc, struct fb_info *info, int mode, ++ int softback_lines, int fg, int bg) +- int fg, int bg) + { + struct fb_cursor cursor; + struct fbcon_ops *ops = info->fbcon_par; +@@ -247,6 +247,15 @@ + + cursor.set = 0; + ++ if (softback_lines) { ++ if (y + softback_lines >= vc->vc_rows) { ++ mode = CM_ERASE; ++ ops->cursor_flash = 0; ++ return; ++ } else ++ y += softback_lines; ++ } ++ + c = scr_readw((u16 *) vc->vc_pos); + attribute = get_attribute(info, c); + src = vc->vc_font.data + ((c & charmask) * (w * vc->vc_font.height)); +--- b/drivers/video/fbdev/core/fbcon.c ++++ a/drivers/video/fbdev/core/fbcon.c +@@ -394,7 +394,7 @@ + c = scr_readw((u16 *) vc->vc_pos); + mode = (!ops->cursor_flash || ops->cursor_state.enable) ? + CM_ERASE : CM_DRAW; ++ ops->cursor(vc, info, mode, 0, get_color(vc, info, c, 1), +- ops->cursor(vc, info, mode, get_color(vc, info, c, 1), + get_color(vc, info, c, 0)); + console_unlock(); + } +@@ -1345,7 +1345,7 @@ + + ops->cursor_flash = (mode == CM_ERASE) ? 0 : 1; + ++ ops->cursor(vc, info, mode, 0, get_color(vc, info, c, 1), +- ops->cursor(vc, info, mode, get_color(vc, info, c, 1), + get_color(vc, info, c, 0)); + } + +--- b/drivers/video/fbdev/core/fbcon.h ++++ a/drivers/video/fbdev/core/fbcon.h +@@ -62,7 +62,7 @@ + void (*clear_margins)(struct vc_data *vc, struct fb_info *info, + int color, int bottom_only); + void (*cursor)(struct vc_data *vc, struct fb_info *info, int mode, ++ int softback_lines, int fg, int bg); +- int fg, int bg); + int (*update_start)(struct fb_info *info); + int (*rotate_font)(struct fb_info *info, struct vc_data *vc); + struct fb_var_screeninfo var; /* copy of the current fb_var_screeninfo */ +--- b/drivers/video/fbdev/core/fbcon_ccw.c ++++ a/drivers/video/fbdev/core/fbcon_ccw.c +@@ -219,7 +219,7 @@ + } + + static void ccw_cursor(struct vc_data *vc, struct fb_info *info, int mode, ++ int softback_lines, int fg, int bg) +- int fg, int bg) + { + struct fb_cursor cursor; + struct fbcon_ops *ops = info->fbcon_par; +@@ -236,6 +236,15 @@ + + cursor.set = 0; + ++ if (softback_lines) { ++ if (y + softback_lines >= vc->vc_rows) { ++ mode = CM_ERASE; ++ ops->cursor_flash = 0; ++ return; ++ } else ++ y += softback_lines; ++ } ++ + c = scr_readw((u16 *) vc->vc_pos); + attribute = get_attribute(info, c); + src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.width)); +--- b/drivers/video/fbdev/core/fbcon_cw.c ++++ a/drivers/video/fbdev/core/fbcon_cw.c +@@ -202,7 +202,7 @@ + } + + static void cw_cursor(struct vc_data *vc, struct fb_info *info, int mode, ++ int softback_lines, int fg, int bg) +- int fg, int bg) + { + struct fb_cursor cursor; + struct fbcon_ops *ops = info->fbcon_par; +@@ -219,6 +219,15 @@ + + cursor.set = 0; + ++ if (softback_lines) { ++ if (y + softback_lines >= vc->vc_rows) { ++ mode = CM_ERASE; ++ ops->cursor_flash = 0; ++ return; ++ } else ++ y += softback_lines; ++ } ++ + c = scr_readw((u16 *) vc->vc_pos); + attribute = get_attribute(info, c); + src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.width)); +--- b/drivers/video/fbdev/core/fbcon_ud.c ++++ a/drivers/video/fbdev/core/fbcon_ud.c +@@ -249,7 +249,7 @@ + } + + static void ud_cursor(struct vc_data *vc, struct fb_info *info, int mode, ++ int softback_lines, int fg, int bg) +- int fg, int bg) + { + struct fb_cursor cursor; + struct fbcon_ops *ops = info->fbcon_par; +@@ -267,6 +267,15 @@ + + cursor.set = 0; + ++ if (softback_lines) { ++ if (y + softback_lines >= vc->vc_rows) { ++ mode = CM_ERASE; ++ ops->cursor_flash = 0; ++ return; ++ } else ++ y += softback_lines; ++ } ++ + c = scr_readw((u16 *) vc->vc_pos); + attribute = get_attribute(info, c); + src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.height)); +--- b/drivers/video/fbdev/core/tileblit.c ++++ a/drivers/video/fbdev/core/tileblit.c +@@ -80,7 +80,7 @@ + } + + static void tile_cursor(struct vc_data *vc, struct fb_info *info, int mode, ++ int softback_lines, int fg, int bg) +- int fg, int bg) + { + struct fb_tilecursor cursor; + int use_sw = (vc->vc_cursor_type & 0x10); diff --git a/recipes-kernel/linux/linux-pinephonepro/0002-sdhci-arasan-Add-runtime-PM-support.patch b/recipes-kernel/linux/linux-pinephonepro/0002-sdhci-arasan-Add-runtime-PM-support.patch new file mode 100644 index 0000000..0ceed66 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0002-sdhci-arasan-Add-runtime-PM-support.patch @@ -0,0 +1,152 @@ +From: Manish Narani +Date: Tue, 18 Sep 2018 20:34:06 +0530 +Subject: [PATCH 16/36] sdhci: arasan: Add runtime PM support + +Add runtime PM support in Arasan SDHCI driver. + +Signed-off-by: Manish Narani +--- + drivers/mmc/host/sdhci-of-arasan.c | 88 +++++++++++++++++++++++++++++++++++++- + 1 file changed, 86 insertions(+), 2 deletions(-) + +diff --git a/drivers/mmc/host/sdhci-of-arasan.c b/drivers/mmc/host/sdhci-of-arasan.c +index 6a2e5a4..e08d6ef 100644 +--- a/drivers/mmc/host/sdhci-of-arasan.c ++++ b/drivers/mmc/host/sdhci-of-arasan.c +@@ -19,6 +19,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -27,6 +28,7 @@ + #include "cqhci.h" + #include "sdhci-pltfm.h" + ++#define SDHCI_ARASAN_AUTOSUSPEND_DELAY 2000 /* ms */ + #define SDHCI_ARASAN_VENDOR_REGISTER 0x78 + + #define SDHCI_ARASAN_ITAPDLY_REGISTER 0xF0F8 +@@ -472,6 +474,70 @@ static const struct sdhci_pltfm_data sdhci_arasan_thunderbay_pdata = { + SDHCI_QUIRK2_CAPS_BIT63_FOR_HS400, + }; + ++#ifdef CONFIG_PM ++static int sdhci_arasan_runtime_suspend(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct sdhci_host *host = platform_get_drvdata(pdev); ++ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); ++ struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host); ++ int ret; ++ ++ if (sdhci_arasan->has_cqe) { ++ ret = cqhci_suspend(host->mmc); ++ if (ret) ++ return ret; ++ } ++ ++ ret = sdhci_runtime_suspend_host(host); ++ if (ret) ++ return ret; ++ ++ if (host->tuning_mode != SDHCI_TUNING_MODE_3) ++ mmc_retune_needed(host->mmc); ++ ++ clk_disable(pltfm_host->clk); ++ clk_disable(sdhci_arasan->clk_ahb); ++ ++ return 0; ++} ++ ++static int sdhci_arasan_runtime_resume(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct sdhci_host *host = platform_get_drvdata(pdev); ++ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); ++ struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host); ++ int ret; ++ ++ ret = clk_enable(sdhci_arasan->clk_ahb); ++ if (ret) { ++ dev_err(dev, "Cannot enable AHB clock.\n"); ++ return ret; ++ } ++ ++ ret = clk_enable(pltfm_host->clk); ++ if (ret) { ++ dev_err(dev, "Cannot enable SD clock.\n"); ++ return ret; ++ } ++ ++ ret = sdhci_runtime_resume_host(host, 0); ++ if (ret) ++ goto out; ++ ++ if (sdhci_arasan->has_cqe) ++ return cqhci_resume(host->mmc); ++ ++ return 0; ++out: ++ clk_disable(pltfm_host->clk); ++ clk_disable(sdhci_arasan->clk_ahb); ++ ++ return ret; ++} ++#endif /* ! CONFIG_PM */ ++ + #ifdef CONFIG_PM_SLEEP + /** + * sdhci_arasan_suspend - Suspend method for the driver +@@ -568,8 +634,10 @@ static int sdhci_arasan_resume(struct device *dev) + } + #endif /* ! CONFIG_PM_SLEEP */ + +-static SIMPLE_DEV_PM_OPS(sdhci_arasan_dev_pm_ops, sdhci_arasan_suspend, +- sdhci_arasan_resume); ++static const struct dev_pm_ops sdhci_arasan_dev_pm_ops = { ++ SET_SYSTEM_SLEEP_PM_OPS(sdhci_arasan_suspend, sdhci_arasan_resume) ++ SET_RUNTIME_PM_OPS(sdhci_arasan_runtime_suspend, ++ sdhci_arasan_runtime_resume, NULL) }; + + /** + * sdhci_arasan_sdcardclk_recalc_rate - Return the card clock rate +@@ -1708,13 +1776,25 @@ static int sdhci_arasan_probe(struct platform_device *pdev) + host->mmc->caps2 |= MMC_CAP2_CQE_DCMD; + } + ++ pm_runtime_get_noresume(&pdev->dev); ++ pm_runtime_set_active(&pdev->dev); ++ pm_runtime_enable(&pdev->dev); ++ pm_runtime_set_autosuspend_delay(&pdev->dev, ++ SDHCI_ARASAN_AUTOSUSPEND_DELAY); ++ pm_runtime_use_autosuspend(&pdev->dev); ++ + ret = sdhci_arasan_add_host(sdhci_arasan); + if (ret) + goto err_add_host; + ++ pm_runtime_put_autosuspend(&pdev->dev); ++ + return 0; + + err_add_host: ++ pm_runtime_disable(&pdev->dev); ++ pm_runtime_set_suspended(&pdev->dev); ++ pm_runtime_put_noidle(&pdev->dev); + if (!IS_ERR(sdhci_arasan->phy)) + phy_exit(sdhci_arasan->phy); + unreg_clk: +@@ -1742,6 +1822,10 @@ static int sdhci_arasan_remove(struct platform_device *pdev) + phy_exit(sdhci_arasan->phy); + } + ++ pm_runtime_get_sync(&pdev->dev); ++ pm_runtime_disable(&pdev->dev); ++ pm_runtime_put_noidle(&pdev->dev); ++ + sdhci_arasan_unregister_sdclk(&pdev->dev); + + ret = sdhci_pltfm_unregister(pdev); diff --git a/recipes-kernel/linux/linux-pinephonepro/0003-bootsplash.patch b/recipes-kernel/linux/linux-pinephonepro/0003-bootsplash.patch new file mode 100644 index 0000000..2169537 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0003-bootsplash.patch @@ -0,0 +1,66 @@ +diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c +index 843c5400fefc..815b007f81ca 100644 +--- a/drivers/video/fbdev/core/bootsplash.c ++++ b/drivers/video/fbdev/core/bootsplash.c +@@ -112,6 +112,8 @@ void bootsplash_render_full(struct fb_info *info) + + bootsplash_do_render_pictures(info, splash_state.file); + ++ bootsplash_do_render_flush(info); ++ + out: + mutex_unlock(&splash_state.data_lock); + } +diff --git a/drivers/video/fbdev/core/bootsplash_internal.h b/drivers/video/fbdev/core/bootsplash_internal.h +index 71e2a27ac0b8..0acb383aa4e3 100644 +--- a/drivers/video/fbdev/core/bootsplash_internal.h ++++ b/drivers/video/fbdev/core/bootsplash_internal.h +@@ -89,6 +89,7 @@ void bootsplash_do_render_background(struct fb_info *info, + const struct splash_file_priv *fp); + void bootsplash_do_render_pictures(struct fb_info *info, + const struct splash_file_priv *fp); ++void bootsplash_do_render_flush(struct fb_info *info); + + + void bootsplash_free_file(struct splash_file_priv *fp); +diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c +index 2ae36949d0e3..8c09c306ff67 100644 +--- a/drivers/video/fbdev/core/bootsplash_render.c ++++ b/drivers/video/fbdev/core/bootsplash_render.c +@@ -186,3 +186,36 @@ void bootsplash_do_render_pictures(struct fb_info *info, + pp->pic_header->width, pp->pic_header->height); + } + } ++ ++ ++void bootsplash_do_render_flush(struct fb_info *info) ++{ ++ /* ++ * FB drivers using deferred_io (such as Xen) need to sync the ++ * screen after modifying its contents. When the FB is mmap()ed ++ * from userspace, this happens via a dirty pages callback, but ++ * when modifying the FB from the kernel, there is no such thing. ++ * ++ * So let's issue a fake fb_copyarea (copying the FB onto itself) ++ * to trick the FB driver into syncing the screen. ++ * ++ * A few DRM drivers' FB implementations are broken by not using ++ * deferred_io when they really should - we match on the known ++ * bad ones manually for now. ++ */ ++ if (info->fbdefio ++ || !strcmp(info->fix.id, "astdrmfb") ++ || !strcmp(info->fix.id, "cirrusdrmfb") ++ || !strcmp(info->fix.id, "mgadrmfb")) { ++ struct fb_copyarea area; ++ ++ area.dx = 0; ++ area.dy = 0; ++ area.width = info->var.xres; ++ area.height = info->var.yres; ++ area.sx = 0; ++ area.sy = 0; ++ ++ info->fbops->fb_copyarea(info, &area); ++ } ++} diff --git a/recipes-kernel/linux/linux-pinephonepro/0003-clk-rk3399-Export-SCLK_CIF_OUT_SRC-to-device-tree.patch b/recipes-kernel/linux/linux-pinephonepro/0003-clk-rk3399-Export-SCLK_CIF_OUT_SRC-to-device-tree.patch new file mode 100644 index 0000000..fd8585a --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0003-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/recipes-kernel/linux/linux-pinephonepro/0003-revert-fbcon-remove-no-op-fbcon_set_origin.patch b/recipes-kernel/linux/linux-pinephonepro/0003-revert-fbcon-remove-no-op-fbcon_set_origin.patch new file mode 100644 index 0000000..6491c54 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0003-revert-fbcon-remove-no-op-fbcon_set_origin.patch @@ -0,0 +1,31 @@ +--- b/drivers/video/fbdev/core/fbcon.c ++++ a/drivers/video/fbdev/core/fbcon.c +@@ -163,6 +163,8 @@ + + #define advance_row(p, delta) (unsigned short *)((unsigned long)(p) + (delta) * vc->vc_size_row) + ++static int fbcon_set_origin(struct vc_data *); ++ + static int fbcon_cursor_noblink; + + #define divides(a, b) ((!(a) || (b)%(a)) ? 0 : 1) +@@ -2633,6 +2635,11 @@ + } + } + ++static int fbcon_set_origin(struct vc_data *vc) ++{ ++ return 0; ++} ++ + void fbcon_suspended(struct fb_info *info) + { + struct vc_data *vc = NULL; +@@ -3103,6 +3110,7 @@ + .con_font_default = fbcon_set_def_font, + .con_font_copy = fbcon_copy_font, + .con_set_palette = fbcon_set_palette, ++ .con_set_origin = fbcon_set_origin, + .con_invert_region = fbcon_invert_region, + .con_screen_pos = fbcon_screen_pos, + .con_getxy = fbcon_getxy, diff --git a/recipes-kernel/linux/linux-pinephonepro/0004-bootsplash.patch b/recipes-kernel/linux/linux-pinephonepro/0004-bootsplash.patch new file mode 100644 index 0000000..7eb54af --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0004-bootsplash.patch @@ -0,0 +1,215 @@ +diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c +index 8c09c306ff67..07e3a4eab811 100644 +--- a/drivers/video/fbdev/core/bootsplash_render.c ++++ b/drivers/video/fbdev/core/bootsplash_render.c +@@ -155,6 +155,7 @@ void bootsplash_do_render_pictures(struct fb_info *info, + for (i = 0; i < fp->header->num_pics; i++) { + struct splash_blob_priv *bp; + struct splash_pic_priv *pp = &fp->pics[i]; ++ const struct splash_pic_header *ph = pp->pic_header; + long dst_xoff, dst_yoff; + + if (pp->blobs_loaded < 1) +@@ -165,8 +166,139 @@ void bootsplash_do_render_pictures(struct fb_info *info, + if (!bp || bp->blob_header->type != 0) + continue; + +- dst_xoff = (info->var.xres - pp->pic_header->width) / 2; +- dst_yoff = (info->var.yres - pp->pic_header->height) / 2; ++ switch (ph->position) { ++ case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_TOP_LEFT: ++ dst_xoff = 0; ++ dst_yoff = 0; ++ ++ dst_xoff += ph->position_offset; ++ dst_yoff += ph->position_offset; ++ break; ++ case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_TOP: ++ dst_xoff = info->var.xres - pp->pic_header->width; ++ dst_xoff /= 2; ++ dst_yoff = 0; ++ ++ dst_yoff += ph->position_offset; ++ break; ++ case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_TOP_RIGHT: ++ dst_xoff = info->var.xres - pp->pic_header->width; ++ dst_yoff = 0; ++ ++ dst_xoff -= ph->position_offset; ++ dst_yoff += ph->position_offset; ++ break; ++ case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_RIGHT: ++ dst_xoff = info->var.xres - pp->pic_header->width; ++ dst_yoff = info->var.yres - pp->pic_header->height; ++ dst_yoff /= 2; ++ ++ dst_xoff -= ph->position_offset; ++ break; ++ case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_BOTTOM_RIGHT: ++ dst_xoff = info->var.xres - pp->pic_header->width; ++ dst_yoff = info->var.yres - pp->pic_header->height; ++ ++ dst_xoff -= ph->position_offset; ++ dst_yoff -= ph->position_offset; ++ break; ++ case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_BOTTOM: ++ dst_xoff = info->var.xres - pp->pic_header->width; ++ dst_xoff /= 2; ++ dst_yoff = info->var.yres - pp->pic_header->height; ++ ++ dst_yoff -= ph->position_offset; ++ break; ++ case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_BOTTOM_LEFT: ++ dst_xoff = 0 + ph->position_offset; ++ dst_yoff = info->var.yres - pp->pic_header->height ++ - ph->position_offset; ++ break; ++ case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_LEFT: ++ dst_xoff = 0; ++ dst_yoff = info->var.yres - pp->pic_header->height; ++ dst_yoff /= 2; ++ ++ dst_xoff += ph->position_offset; ++ break; ++ ++ case SPLASH_CORNER_TOP_LEFT: ++ dst_xoff = info->var.xres - pp->pic_header->width; ++ dst_xoff /= 2; ++ dst_yoff = info->var.yres - pp->pic_header->height; ++ dst_yoff /= 2; ++ ++ dst_xoff -= ph->position_offset; ++ dst_yoff -= ph->position_offset; ++ break; ++ case SPLASH_CORNER_TOP: ++ dst_xoff = info->var.xres - pp->pic_header->width; ++ dst_xoff /= 2; ++ dst_yoff = info->var.yres - pp->pic_header->height; ++ dst_yoff /= 2; ++ ++ dst_yoff -= ph->position_offset; ++ break; ++ case SPLASH_CORNER_TOP_RIGHT: ++ dst_xoff = info->var.xres - pp->pic_header->width; ++ dst_xoff /= 2; ++ dst_yoff = info->var.yres - pp->pic_header->height; ++ dst_yoff /= 2; ++ ++ dst_xoff += ph->position_offset; ++ dst_yoff -= ph->position_offset; ++ break; ++ case SPLASH_CORNER_RIGHT: ++ dst_xoff = info->var.xres - pp->pic_header->width; ++ dst_xoff /= 2; ++ dst_yoff = info->var.yres - pp->pic_header->height; ++ dst_yoff /= 2; ++ ++ dst_xoff += ph->position_offset; ++ break; ++ case SPLASH_CORNER_BOTTOM_RIGHT: ++ dst_xoff = info->var.xres - pp->pic_header->width; ++ dst_xoff /= 2; ++ dst_yoff = info->var.yres - pp->pic_header->height; ++ dst_yoff /= 2; ++ ++ dst_xoff += ph->position_offset; ++ dst_yoff += ph->position_offset; ++ break; ++ case SPLASH_CORNER_BOTTOM: ++ dst_xoff = info->var.xres - pp->pic_header->width; ++ dst_xoff /= 2; ++ dst_yoff = info->var.yres - pp->pic_header->height; ++ dst_yoff /= 2; ++ ++ dst_yoff += ph->position_offset; ++ break; ++ case SPLASH_CORNER_BOTTOM_LEFT: ++ dst_xoff = info->var.xres - pp->pic_header->width; ++ dst_xoff /= 2; ++ dst_yoff = info->var.yres - pp->pic_header->height; ++ dst_yoff /= 2; ++ ++ dst_xoff -= ph->position_offset; ++ dst_yoff += ph->position_offset; ++ break; ++ case SPLASH_CORNER_LEFT: ++ dst_xoff = info->var.xres - pp->pic_header->width; ++ dst_xoff /= 2; ++ dst_yoff = info->var.yres - pp->pic_header->height; ++ dst_yoff /= 2; ++ ++ dst_xoff -= ph->position_offset; ++ break; ++ ++ default: ++ /* As a fallback, center the picture. */ ++ dst_xoff = info->var.xres - pp->pic_header->width; ++ dst_xoff /= 2; ++ dst_yoff = info->var.yres - pp->pic_header->height; ++ dst_yoff /= 2; ++ break; ++ } + + if (dst_xoff < 0 + || dst_yoff < 0 +diff --git a/include/uapi/linux/bootsplash_file.h b/include/uapi/linux/bootsplash_file.h +index 89dc9cca8f0c..71cedcc68933 100644 +--- a/include/uapi/linux/bootsplash_file.h ++++ b/include/uapi/linux/bootsplash_file.h +@@ -91,7 +91,32 @@ struct splash_pic_header { + */ + uint8_t num_blobs; + +- uint8_t padding[27]; ++ /* ++ * Corner to move the picture to / from. ++ * 0x00 - Top left ++ * 0x01 - Top ++ * 0x02 - Top right ++ * 0x03 - Right ++ * 0x04 - Bottom right ++ * 0x05 - Bottom ++ * 0x06 - Bottom left ++ * 0x07 - Left ++ * ++ * Flags: ++ * 0x10 - Calculate offset from the corner towards the center, ++ * rather than from the center towards the corner ++ */ ++ uint8_t position; ++ ++ /* ++ * Pixel offset from the selected position. ++ * Example: If the picture is in the top right corner, it will ++ * be placed position_offset pixels from the top and ++ * position_offset pixels from the right margin. ++ */ ++ uint16_t position_offset; ++ ++ uint8_t padding[24]; + } __attribute__((__packed__)); + + +@@ -115,4 +140,22 @@ struct splash_blob_header { + uint8_t padding[9]; + } __attribute__((__packed__)); + ++ ++ ++ ++/* ++ * Enums for on-disk types ++ */ ++enum splash_position { ++ SPLASH_CORNER_TOP_LEFT = 0, ++ SPLASH_CORNER_TOP = 1, ++ SPLASH_CORNER_TOP_RIGHT = 2, ++ SPLASH_CORNER_RIGHT = 3, ++ SPLASH_CORNER_BOTTOM_RIGHT = 4, ++ SPLASH_CORNER_BOTTOM = 5, ++ SPLASH_CORNER_BOTTOM_LEFT = 6, ++ SPLASH_CORNER_LEFT = 7, ++ SPLASH_POS_FLAG_CORNER = 0x10, ++}; ++ + #endif diff --git a/recipes-kernel/linux/linux-pinephonepro/0004-media-rockchip-rga-Fix-probe-bugs.patch b/recipes-kernel/linux/linux-pinephonepro/0004-media-rockchip-rga-Fix-probe-bugs.patch new file mode 100644 index 0000000..a0e574f --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0004-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/recipes-kernel/linux/linux-pinephonepro/0004-revert-fbcon-remove-soft-scrollback-code.patch b/recipes-kernel/linux/linux-pinephonepro/0004-revert-fbcon-remove-soft-scrollback-code.patch new file mode 100644 index 0000000..4f97354 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0004-revert-fbcon-remove-soft-scrollback-code.patch @@ -0,0 +1,500 @@ +--- b/drivers/video/fbdev/core/fbcon.c ++++ a/drivers/video/fbdev/core/fbcon.c +@@ -122,6 +122,12 @@ + /* logo_shown is an index to vc_cons when >= 0; otherwise follows FBCON_LOGO + enums. */ + static int logo_shown = FBCON_LOGO_CANSHOW; ++/* Software scrollback */ ++static int fbcon_softback_size = 32768; ++static unsigned long softback_buf, softback_curr; ++static unsigned long softback_in; ++static unsigned long softback_top, softback_end; ++static int softback_lines; + /* console mappings */ + static int first_fb_vc; + static int last_fb_vc = MAX_NR_CONSOLES - 1; +@@ -161,6 +167,8 @@ + + static const struct consw fb_con; + ++#define CM_SOFTBACK (8) ++ + #define advance_row(p, delta) (unsigned short *)((unsigned long)(p) + (delta) * vc->vc_size_row) + + static int fbcon_set_origin(struct vc_data *); +@@ -365,6 +373,18 @@ + return color; + } + ++static void fbcon_update_softback(struct vc_data *vc) ++{ ++ int l = fbcon_softback_size / vc->vc_size_row; ++ ++ if (l > 5) ++ softback_end = softback_buf + l * vc->vc_size_row; ++ else ++ /* Smaller scrollback makes no sense, and 0 would screw ++ the operation totally */ ++ softback_top = 0; ++} ++ + static void fb_flashcursor(struct work_struct *work) + { + struct fb_info *info = container_of(work, struct fb_info, queue); +@@ -394,7 +414,7 @@ + c = scr_readw((u16 *) vc->vc_pos); + mode = (!ops->cursor_flash || ops->cursor_state.enable) ? + CM_ERASE : CM_DRAW; ++ ops->cursor(vc, info, mode, softback_lines, get_color(vc, info, c, 1), +- ops->cursor(vc, info, mode, 0, get_color(vc, info, c, 1), + get_color(vc, info, c, 0)); + console_unlock(); + } +@@ -451,7 +471,13 @@ + } + + if (!strncmp(options, "scrollback:", 11)) { ++ options += 11; ++ if (*options) { ++ fbcon_softback_size = simple_strtoul(options, &options, 0); ++ if (*options == 'k' || *options == 'K') { ++ fbcon_softback_size *= 1024; ++ } ++ } +- pr_warn("Ignoring scrollback size option\n"); + continue; + } + +@@ -996,6 +1022,31 @@ + + set_blitting_type(vc, info); + ++ if (info->fix.type != FB_TYPE_TEXT) { ++ if (fbcon_softback_size) { ++ if (!softback_buf) { ++ softback_buf = ++ (unsigned long) ++ kvmalloc(fbcon_softback_size, ++ GFP_KERNEL); ++ if (!softback_buf) { ++ fbcon_softback_size = 0; ++ softback_top = 0; ++ } ++ } ++ } else { ++ if (softback_buf) { ++ kvfree((void *) softback_buf); ++ softback_buf = 0; ++ softback_top = 0; ++ } ++ } ++ if (softback_buf) ++ softback_in = softback_top = softback_curr = ++ softback_buf; ++ softback_lines = 0; ++ } ++ + /* Setup default font */ + if (!p->fontdata && !vc->vc_font.data) { + if (!fontname[0] || !(font = find_font(fontname))) +@@ -1169,6 +1220,9 @@ + if (logo) + fbcon_prepare_logo(vc, info, cols, rows, new_cols, new_rows); + ++ if (vc == svc && softback_buf) ++ fbcon_update_softback(vc); ++ + if (ops->rotate_font && ops->rotate_font(info, vc)) { + ops->rotate = FB_ROTATE_UR; + set_blitting_type(vc, info); +@@ -1331,6 +1385,7 @@ + { + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; + struct fbcon_ops *ops = info->fbcon_par; ++ int y; + int c = scr_readw((u16 *) vc->vc_pos); + + ops->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms); +@@ -1334,11 +1389,19 @@ static void fbcon_cursor(struct vc_data + fbcon_add_cursor_timer(info); + + ops->cursor_flash = (mode == CM_ERASE) ? 0 : 1; ++ if (mode & CM_SOFTBACK) { ++ mode &= ~CM_SOFTBACK; ++ y = softback_lines; ++ } else { ++ if (softback_lines) ++ fbcon_set_origin(vc); ++ y = 0; ++ } + + if (!ops->cursor) + return; + +- ops->cursor(vc, info, mode, 0, get_color(vc, info, c, 1), ++ ops->cursor(vc, info, mode, y, get_color(vc, info, c, 1), + get_color(vc, info, c, 0)); + } + +@@ -1416,6 +1479,8 @@ + + if (con_is_visible(vc)) { + update_screen(vc); ++ if (softback_buf) ++ fbcon_update_softback(vc); + } + } + +@@ -1553,6 +1618,99 @@ + scrollback_current = 0; + } + ++static void fbcon_redraw_softback(struct vc_data *vc, struct fbcon_display *p, ++ long delta) ++{ ++ int count = vc->vc_rows; ++ unsigned short *d, *s; ++ unsigned long n; ++ int line = 0; ++ ++ d = (u16 *) softback_curr; ++ if (d == (u16 *) softback_in) ++ d = (u16 *) vc->vc_origin; ++ n = softback_curr + delta * vc->vc_size_row; ++ softback_lines -= delta; ++ if (delta < 0) { ++ if (softback_curr < softback_top && n < softback_buf) { ++ n += softback_end - softback_buf; ++ if (n < softback_top) { ++ softback_lines -= ++ (softback_top - n) / vc->vc_size_row; ++ n = softback_top; ++ } ++ } else if (softback_curr >= softback_top ++ && n < softback_top) { ++ softback_lines -= ++ (softback_top - n) / vc->vc_size_row; ++ n = softback_top; ++ } ++ } else { ++ if (softback_curr > softback_in && n >= softback_end) { ++ n += softback_buf - softback_end; ++ if (n > softback_in) { ++ n = softback_in; ++ softback_lines = 0; ++ } ++ } else if (softback_curr <= softback_in && n > softback_in) { ++ n = softback_in; ++ softback_lines = 0; ++ } ++ } ++ if (n == softback_curr) ++ return; ++ softback_curr = n; ++ s = (u16 *) softback_curr; ++ if (s == (u16 *) softback_in) ++ s = (u16 *) vc->vc_origin; ++ while (count--) { ++ unsigned short *start; ++ unsigned short *le; ++ unsigned short c; ++ int x = 0; ++ unsigned short attr = 1; ++ ++ start = s; ++ le = advance_row(s, 1); ++ do { ++ c = scr_readw(s); ++ if (attr != (c & 0xff00)) { ++ attr = c & 0xff00; ++ if (s > start) { ++ fbcon_putcs(vc, start, s - start, ++ line, x); ++ x += s - start; ++ start = s; ++ } ++ } ++ if (c == scr_readw(d)) { ++ if (s > start) { ++ fbcon_putcs(vc, start, s - start, ++ line, x); ++ x += s - start + 1; ++ start = s + 1; ++ } else { ++ x++; ++ start++; ++ } ++ } ++ s++; ++ d++; ++ } while (s < le); ++ if (s > start) ++ fbcon_putcs(vc, start, s - start, line, x); ++ line++; ++ if (d == (u16 *) softback_end) ++ d = (u16 *) softback_buf; ++ if (d == (u16 *) softback_in) ++ d = (u16 *) vc->vc_origin; ++ if (s == (u16 *) softback_end) ++ s = (u16 *) softback_buf; ++ if (s == (u16 *) softback_in) ++ s = (u16 *) vc->vc_origin; ++ } ++} ++ + static void fbcon_redraw_move(struct vc_data *vc, struct fbcon_display *p, + int line, int count, int dy) + { +@@ -1692,6 +1850,31 @@ + } + } + ++static inline void fbcon_softback_note(struct vc_data *vc, int t, ++ int count) ++{ ++ unsigned short *p; ++ ++ if (vc->vc_num != fg_console) ++ return; ++ p = (unsigned short *) (vc->vc_origin + t * vc->vc_size_row); ++ ++ while (count) { ++ scr_memcpyw((u16 *) softback_in, p, vc->vc_size_row); ++ count--; ++ p = advance_row(p, 1); ++ softback_in += vc->vc_size_row; ++ if (softback_in == softback_end) ++ softback_in = softback_buf; ++ if (softback_in == softback_top) { ++ softback_top += vc->vc_size_row; ++ if (softback_top == softback_end) ++ softback_top = softback_buf; ++ } ++ } ++ softback_curr = softback_in; ++} ++ + static bool fbcon_scroll(struct vc_data *vc, unsigned int t, unsigned int b, + enum con_scroll dir, unsigned int count) + { +@@ -1714,6 +1897,8 @@ + case SM_UP: + if (count > vc->vc_rows) /* Maximum realistic size */ + count = vc->vc_rows; ++ if (softback_top) ++ fbcon_softback_note(vc, t, count); + if (logo_shown >= 0) + goto redraw_up; + switch (p->scrollmode) { +@@ -2084,6 +2269,14 @@ + info = registered_fb[con2fb_map[vc->vc_num]]; + ops = info->fbcon_par; + ++ if (softback_top) { ++ if (softback_lines) ++ fbcon_set_origin(vc); ++ softback_top = softback_curr = softback_in = softback_buf; ++ softback_lines = 0; ++ fbcon_update_softback(vc); ++ } ++ + if (logo_shown >= 0) { + struct vc_data *conp2 = vc_cons[logo_shown].d; + +@@ -2407,6 +2600,9 @@ + int cnt; + char *old_data = NULL; + ++ if (con_is_visible(vc) && softback_lines) ++ fbcon_set_origin(vc); ++ + resize = (w != vc->vc_font.width) || (h != vc->vc_font.height); + if (p->userfont) + old_data = vc->vc_font.data; +@@ -2432,6 +2628,8 @@ + cols /= w; + rows /= h; + vc_resize(vc, cols, rows); ++ if (con_is_visible(vc) && softback_buf) ++ fbcon_update_softback(vc); + } else if (con_is_visible(vc) + && vc->vc_mode == KD_TEXT) { + fbcon_clear_margins(vc, 0); +@@ -2590,7 +2788,19 @@ + + static u16 *fbcon_screen_pos(struct vc_data *vc, int offset) + { ++ unsigned long p; ++ int line; ++ ++ if (vc->vc_num != fg_console || !softback_lines) ++ return (u16 *) (vc->vc_origin + offset); ++ line = offset / vc->vc_size_row; ++ if (line >= softback_lines) ++ return (u16 *) (vc->vc_origin + offset - ++ softback_lines * vc->vc_size_row); ++ p = softback_curr + offset; ++ if (p >= softback_end) ++ p += softback_buf - softback_end; ++ return (u16 *) p; +- return (u16 *) (vc->vc_origin + offset); + } + + static unsigned long fbcon_getxy(struct vc_data *vc, unsigned long pos, +@@ -2604,7 +2814,22 @@ + + x = offset % vc->vc_cols; + y = offset / vc->vc_cols; ++ if (vc->vc_num == fg_console) ++ y += softback_lines; + ret = pos + (vc->vc_cols - x) * 2; ++ } else if (vc->vc_num == fg_console && softback_lines) { ++ unsigned long offset = pos - softback_curr; ++ ++ if (pos < softback_curr) ++ offset += softback_end - softback_buf; ++ offset /= 2; ++ x = offset % vc->vc_cols; ++ y = offset / vc->vc_cols; ++ ret = pos + (vc->vc_cols - x) * 2; ++ if (ret == softback_end) ++ ret = softback_buf; ++ if (ret == softback_in) ++ ret = vc->vc_origin; + } else { + /* Should not happen */ + x = y = 0; +@@ -2632,11 +2857,106 @@ + a = ((a) & 0x88ff) | (((a) & 0x7000) >> 4) | + (((a) & 0x0700) << 4); + scr_writew(a, p++); ++ if (p == (u16 *) softback_end) ++ p = (u16 *) softback_buf; ++ if (p == (u16 *) softback_in) ++ p = (u16 *) vc->vc_origin; ++ } ++} ++ ++static void fbcon_scrolldelta(struct vc_data *vc, int lines) ++{ ++ struct fb_info *info = registered_fb[con2fb_map[fg_console]]; ++ struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_display *disp = &fb_display[fg_console]; ++ int offset, limit, scrollback_old; ++ ++ if (softback_top) { ++ if (vc->vc_num != fg_console) ++ return; ++ if (vc->vc_mode != KD_TEXT || !lines) ++ return; ++ if (logo_shown >= 0) { ++ struct vc_data *conp2 = vc_cons[logo_shown].d; ++ ++ if (conp2->vc_top == logo_lines ++ && conp2->vc_bottom == conp2->vc_rows) ++ conp2->vc_top = 0; ++ if (logo_shown == vc->vc_num) { ++ unsigned long p, q; ++ int i; ++ ++ p = softback_in; ++ q = vc->vc_origin + ++ logo_lines * vc->vc_size_row; ++ for (i = 0; i < logo_lines; i++) { ++ if (p == softback_top) ++ break; ++ if (p == softback_buf) ++ p = softback_end; ++ p -= vc->vc_size_row; ++ q -= vc->vc_size_row; ++ scr_memcpyw((u16 *) q, (u16 *) p, ++ vc->vc_size_row); ++ } ++ softback_in = softback_curr = p; ++ update_region(vc, vc->vc_origin, ++ logo_lines * vc->vc_cols); ++ } ++ logo_shown = FBCON_LOGO_CANSHOW; ++ } ++ fbcon_cursor(vc, CM_ERASE | CM_SOFTBACK); ++ fbcon_redraw_softback(vc, disp, lines); ++ fbcon_cursor(vc, CM_DRAW | CM_SOFTBACK); ++ return; + } ++ ++ if (!scrollback_phys_max) ++ return; ++ ++ scrollback_old = scrollback_current; ++ scrollback_current -= lines; ++ if (scrollback_current < 0) ++ scrollback_current = 0; ++ else if (scrollback_current > scrollback_max) ++ scrollback_current = scrollback_max; ++ if (scrollback_current == scrollback_old) ++ return; ++ ++ if (fbcon_is_inactive(vc, info)) ++ return; ++ ++ fbcon_cursor(vc, CM_ERASE); ++ ++ offset = disp->yscroll - scrollback_current; ++ limit = disp->vrows; ++ switch (disp->scrollmode) { ++ case SCROLL_WRAP_MOVE: ++ info->var.vmode |= FB_VMODE_YWRAP; ++ break; ++ case SCROLL_PAN_MOVE: ++ case SCROLL_PAN_REDRAW: ++ limit -= vc->vc_rows; ++ info->var.vmode &= ~FB_VMODE_YWRAP; ++ break; ++ } ++ if (offset < 0) ++ offset += limit; ++ else if (offset >= limit) ++ offset -= limit; ++ ++ ops->var.xoffset = 0; ++ ops->var.yoffset = offset * vc->vc_font.height; ++ ops->update_start(info); ++ ++ if (!scrollback_current) ++ fbcon_cursor(vc, CM_DRAW); + } + + static int fbcon_set_origin(struct vc_data *vc) + { ++ if (softback_lines) ++ fbcon_scrolldelta(vc, softback_lines); + return 0; + } + +@@ -2700,6 +3020,8 @@ + + fbcon_set_palette(vc, color_table); + update_screen(vc); ++ if (softback_buf) ++ fbcon_update_softback(vc); + } + } + +@@ -3110,6 +3432,7 @@ + .con_font_default = fbcon_set_def_font, + .con_font_copy = fbcon_copy_font, + .con_set_palette = fbcon_set_palette, ++ .con_scrolldelta = fbcon_scrolldelta, + .con_set_origin = fbcon_set_origin, + .con_invert_region = fbcon_invert_region, + .con_screen_pos = fbcon_screen_pos, +@@ -3344,6 +3667,9 @@ + } + #endif + ++ kvfree((void *)softback_buf); ++ softback_buf = 0UL; ++ + for_each_registered_fb(i) { + int pending = 0; + diff --git a/recipes-kernel/linux/linux-pinephonepro/0005-bootsplash.patch b/recipes-kernel/linux/linux-pinephonepro/0005-bootsplash.patch new file mode 100644 index 0000000..2785c5e --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0005-bootsplash.patch @@ -0,0 +1,327 @@ +diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c +index 815b007f81ca..c8642142cfea 100644 +--- a/drivers/video/fbdev/core/bootsplash.c ++++ b/drivers/video/fbdev/core/bootsplash.c +@@ -53,6 +53,14 @@ static void splash_callback_redraw_vc(struct work_struct *ignored) + console_unlock(); + } + ++static void splash_callback_animation(struct work_struct *ignored) ++{ ++ if (bootsplash_would_render_now()) { ++ /* This will also re-schedule this delayed worker */ ++ splash_callback_redraw_vc(ignored); ++ } ++} ++ + + static bool is_fb_compatible(const struct fb_info *info) + { +@@ -103,17 +111,44 @@ static bool is_fb_compatible(const struct fb_info *info) + */ + void bootsplash_render_full(struct fb_info *info) + { ++ bool is_update = false; ++ + mutex_lock(&splash_state.data_lock); + +- if (!is_fb_compatible(info)) +- goto out; ++ /* ++ * If we've painted on this FB recently, we don't have to do ++ * the sanity checks and background drawing again. ++ */ ++ if (splash_state.splash_fb == info) ++ is_update = true; ++ ++ ++ if (!is_update) { ++ /* Check whether we actually support this FB. */ ++ splash_state.splash_fb = NULL; ++ ++ if (!is_fb_compatible(info)) ++ goto out; ++ ++ /* Draw the background only once */ ++ bootsplash_do_render_background(info, splash_state.file); + +- bootsplash_do_render_background(info, splash_state.file); ++ /* Mark this FB as last seen */ ++ splash_state.splash_fb = info; ++ } + +- bootsplash_do_render_pictures(info, splash_state.file); ++ bootsplash_do_render_pictures(info, splash_state.file, is_update); + + bootsplash_do_render_flush(info); + ++ bootsplash_do_step_animations(splash_state.file); ++ ++ /* Schedule update for animated splash screens */ ++ if (splash_state.file->frame_ms > 0) ++ schedule_delayed_work(&splash_state.dwork_animation, ++ msecs_to_jiffies( ++ splash_state.file->frame_ms)); ++ + out: + mutex_unlock(&splash_state.data_lock); + } +@@ -169,8 +204,14 @@ void bootsplash_enable(void) + + was_enabled = test_and_set_bit(0, &splash_state.enabled); + +- if (!was_enabled) ++ if (!was_enabled) { ++ /* Force a full redraw when the splash is re-activated */ ++ mutex_lock(&splash_state.data_lock); ++ splash_state.splash_fb = NULL; ++ mutex_unlock(&splash_state.data_lock); ++ + schedule_work(&splash_state.work_redraw_vc); ++ } + } + + +@@ -227,6 +268,14 @@ ATTRIBUTE_GROUPS(splash_dev); + */ + static int splash_resume(struct device *device) + { ++ /* ++ * Force full redraw on resume since we've probably lost the ++ * framebuffer's contents meanwhile ++ */ ++ mutex_lock(&splash_state.data_lock); ++ splash_state.splash_fb = NULL; ++ mutex_unlock(&splash_state.data_lock); ++ + if (bootsplash_would_render_now()) + schedule_work(&splash_state.work_redraw_vc); + +@@ -235,6 +284,7 @@ static int splash_resume(struct device *device) + + static int splash_suspend(struct device *device) + { ++ cancel_delayed_work_sync(&splash_state.dwork_animation); + cancel_work_sync(&splash_state.work_redraw_vc); + + return 0; +@@ -296,6 +346,8 @@ void bootsplash_init(void) + set_bit(0, &splash_state.enabled); + + INIT_WORK(&splash_state.work_redraw_vc, splash_callback_redraw_vc); ++ INIT_DELAYED_WORK(&splash_state.dwork_animation, ++ splash_callback_animation); + + + if (!splash_state.bootfile || !strlen(splash_state.bootfile)) +diff --git a/drivers/video/fbdev/core/bootsplash_internal.h b/drivers/video/fbdev/core/bootsplash_internal.h +index 0acb383aa4e3..b3a74835d90f 100644 +--- a/drivers/video/fbdev/core/bootsplash_internal.h ++++ b/drivers/video/fbdev/core/bootsplash_internal.h +@@ -37,6 +37,8 @@ struct splash_pic_priv { + + struct splash_blob_priv *blobs; + u16 blobs_loaded; ++ ++ u16 anim_nextframe; + }; + + +@@ -45,6 +47,12 @@ struct splash_file_priv { + const struct splash_file_header *header; + + struct splash_pic_priv *pics; ++ ++ /* ++ * A local copy of the frame delay in the header. ++ * We modify it to keep the code simple. ++ */ ++ u16 frame_ms; + }; + + +@@ -71,6 +79,7 @@ struct splash_priv { + struct platform_device *splash_device; + + struct work_struct work_redraw_vc; ++ struct delayed_work dwork_animation; + + /* Splash data structures including lock for everything below */ + struct mutex data_lock; +@@ -88,8 +97,10 @@ struct splash_priv { + void bootsplash_do_render_background(struct fb_info *info, + const struct splash_file_priv *fp); + void bootsplash_do_render_pictures(struct fb_info *info, +- const struct splash_file_priv *fp); ++ const struct splash_file_priv *fp, ++ bool is_update); + void bootsplash_do_render_flush(struct fb_info *info); ++void bootsplash_do_step_animations(struct splash_file_priv *fp); + + + void bootsplash_free_file(struct splash_file_priv *fp); +diff --git a/drivers/video/fbdev/core/bootsplash_load.c b/drivers/video/fbdev/core/bootsplash_load.c +index fd807571ab7d..1f661b2d4cc9 100644 +--- a/drivers/video/fbdev/core/bootsplash_load.c ++++ b/drivers/video/fbdev/core/bootsplash_load.c +@@ -71,6 +71,7 @@ struct splash_file_priv *bootsplash_load_firmware(struct device *device, + { + const struct firmware *fw; + struct splash_file_priv *fp; ++ bool have_anim = false; + unsigned int i; + const u8 *walker; + +@@ -135,6 +136,13 @@ struct splash_file_priv *bootsplash_load_firmware(struct device *device, + goto err; + } + ++ if (ph->anim_type > SPLASH_ANIM_LOOP_FORWARD) { ++ pr_warn("Picture %u: Unsupported animation type %u.\n", ++ i, ph->anim_type); ++ ++ ph->anim_type = SPLASH_ANIM_NONE; ++ } ++ + pp->pic_header = ph; + pp->blobs = vzalloc(ph->num_blobs + * sizeof(struct splash_blob_priv)); +@@ -202,6 +210,7 @@ struct splash_file_priv *bootsplash_load_firmware(struct device *device, + /* Walk over pictures and ensure all blob slots are filled */ + for (i = 0; i < fp->header->num_pics; i++) { + struct splash_pic_priv *pp = &fp->pics[i]; ++ const struct splash_pic_header *ph = pp->pic_header; + + if (pp->blobs_loaded != pp->pic_header->num_blobs) { + pr_err("Picture %u doesn't have all blob slots filled.\n", +@@ -209,8 +218,20 @@ struct splash_file_priv *bootsplash_load_firmware(struct device *device, + + goto err; + } ++ ++ if (ph->anim_type ++ && ph->num_blobs > 1 ++ && ph->anim_loop < pp->blobs_loaded) ++ have_anim = true; + } + ++ if (!have_anim) ++ /* Disable animation timer if there is nothing to animate */ ++ fp->frame_ms = 0; ++ else ++ /* Enforce minimum delay between frames */ ++ fp->frame_ms = max((u16)20, fp->header->frame_ms); ++ + pr_info("Loaded (%ld bytes, %u pics, %u blobs).\n", + fw->size, + fp->header->num_pics, +diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c +index 07e3a4eab811..76033606ca8a 100644 +--- a/drivers/video/fbdev/core/bootsplash_render.c ++++ b/drivers/video/fbdev/core/bootsplash_render.c +@@ -148,7 +148,8 @@ void bootsplash_do_render_background(struct fb_info *info, + + + void bootsplash_do_render_pictures(struct fb_info *info, +- const struct splash_file_priv *fp) ++ const struct splash_file_priv *fp, ++ bool is_update) + { + unsigned int i; + +@@ -161,7 +162,11 @@ void bootsplash_do_render_pictures(struct fb_info *info, + if (pp->blobs_loaded < 1) + continue; + +- bp = &pp->blobs[0]; ++ /* Skip static pictures when refreshing animations */ ++ if (ph->anim_type == SPLASH_ANIM_NONE && is_update) ++ continue; ++ ++ bp = &pp->blobs[pp->anim_nextframe]; + + if (!bp || bp->blob_header->type != 0) + continue; +@@ -351,3 +356,24 @@ void bootsplash_do_render_flush(struct fb_info *info) + info->fbops->fb_copyarea(info, &area); + } + } ++ ++ ++void bootsplash_do_step_animations(struct splash_file_priv *fp) ++{ ++ unsigned int i; ++ ++ /* Step every animation once */ ++ for (i = 0; i < fp->header->num_pics; i++) { ++ struct splash_pic_priv *pp = &fp->pics[i]; ++ ++ if (pp->blobs_loaded < 2 ++ || pp->pic_header->anim_loop > pp->blobs_loaded) ++ continue; ++ ++ if (pp->pic_header->anim_type == SPLASH_ANIM_LOOP_FORWARD) { ++ pp->anim_nextframe++; ++ if (pp->anim_nextframe >= pp->pic_header->num_blobs) ++ pp->anim_nextframe = pp->pic_header->anim_loop; ++ } ++ } ++} +diff --git a/include/uapi/linux/bootsplash_file.h b/include/uapi/linux/bootsplash_file.h +index 71cedcc68933..b3af0a3c6487 100644 +--- a/include/uapi/linux/bootsplash_file.h ++++ b/include/uapi/linux/bootsplash_file.h +@@ -77,7 +77,17 @@ struct splash_file_header { + uint16_t num_blobs; + uint8_t num_pics; + +- uint8_t padding[103]; ++ uint8_t unused_1; ++ ++ /* ++ * Milliseconds to wait before painting the next frame in ++ * an animation. ++ * This is actually a minimum, as the system is allowed to ++ * stall for longer between frames. ++ */ ++ uint16_t frame_ms; ++ ++ uint8_t padding[100]; + } __attribute__((__packed__)); + + +@@ -116,7 +126,23 @@ struct splash_pic_header { + */ + uint16_t position_offset; + +- uint8_t padding[24]; ++ /* ++ * Animation type. ++ * 0 - off ++ * 1 - forward loop ++ */ ++ uint8_t anim_type; ++ ++ /* ++ * Animation loop point. ++ * Actual meaning depends on animation type: ++ * Type 0 - Unused ++ * 1 - Frame at which to restart the forward loop ++ * (allowing for "intro" frames) ++ */ ++ uint8_t anim_loop; ++ ++ uint8_t padding[22]; + } __attribute__((__packed__)); + + +@@ -158,4 +184,9 @@ enum splash_position { + SPLASH_POS_FLAG_CORNER = 0x10, + }; + ++enum splash_anim_type { ++ SPLASH_ANIM_NONE = 0, ++ SPLASH_ANIM_LOOP_FORWARD = 1, ++}; ++ + #endif diff --git a/recipes-kernel/linux/linux-pinephonepro/0005-drm-dw-mipi-dsi-rockchip-Ensure-that-lane-is-properl.patch b/recipes-kernel/linux/linux-pinephonepro/0005-drm-dw-mipi-dsi-rockchip-Ensure-that-lane-is-properl.patch new file mode 100644 index 0000000..686dc3e --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0005-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/recipes-kernel/linux/linux-pinephonepro/0006-bootsplash.patch b/recipes-kernel/linux/linux-pinephonepro/0006-bootsplash.patch new file mode 100644 index 0000000..d6c6db6 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0006-bootsplash.patch @@ -0,0 +1,82 @@ +diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c +index 2ebaba16f785..416735ab6dc1 100644 +--- a/drivers/tty/vt/vt.c ++++ b/drivers/tty/vt/vt.c +@@ -105,6 +105,7 @@ + #include + #include + #include ++#include + + #define MAX_NR_CON_DRIVER 16 + +@@ -4235,6 +4236,7 @@ void do_unblank_screen(int leaving_gfx) + } + + console_blanked = 0; ++ bootsplash_mark_dirty(); + if (vc->vc_sw->con_blank(vc, 0, leaving_gfx)) + /* Low-level driver cannot restore -> do it ourselves */ + update_screen(vc); +diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c +index c8642142cfea..13fcaabbc2ca 100644 +--- a/drivers/video/fbdev/core/bootsplash.c ++++ b/drivers/video/fbdev/core/bootsplash.c +@@ -165,6 +165,13 @@ bool bootsplash_would_render_now(void) + && bootsplash_is_enabled(); + } + ++void bootsplash_mark_dirty(void) ++{ ++ mutex_lock(&splash_state.data_lock); ++ splash_state.splash_fb = NULL; ++ mutex_unlock(&splash_state.data_lock); ++} ++ + bool bootsplash_is_enabled(void) + { + bool was_enabled; +@@ -206,9 +213,7 @@ void bootsplash_enable(void) + + if (!was_enabled) { + /* Force a full redraw when the splash is re-activated */ +- mutex_lock(&splash_state.data_lock); +- splash_state.splash_fb = NULL; +- mutex_unlock(&splash_state.data_lock); ++ bootsplash_mark_dirty(); + + schedule_work(&splash_state.work_redraw_vc); + } +@@ -272,9 +277,7 @@ static int splash_resume(struct device *device) + * Force full redraw on resume since we've probably lost the + * framebuffer's contents meanwhile + */ +- mutex_lock(&splash_state.data_lock); +- splash_state.splash_fb = NULL; +- mutex_unlock(&splash_state.data_lock); ++ bootsplash_mark_dirty(); + + if (bootsplash_would_render_now()) + schedule_work(&splash_state.work_redraw_vc); +diff --git a/include/linux/bootsplash.h b/include/linux/bootsplash.h +index c6dd0b43180d..4075098aaadd 100644 +--- a/include/linux/bootsplash.h ++++ b/include/linux/bootsplash.h +@@ -19,6 +19,8 @@ extern void bootsplash_render_full(struct fb_info *info); + + extern bool bootsplash_would_render_now(void); + ++extern void bootsplash_mark_dirty(void); ++ + extern bool bootsplash_is_enabled(void); + extern void bootsplash_disable(void); + extern void bootsplash_enable(void); +@@ -31,6 +33,8 @@ extern void bootsplash_init(void); + + #define bootsplash_would_render_now() (false) + ++#define bootsplash_mark_dirty() ++ + #define bootsplash_is_enabled() (false) + #define bootsplash_disable() + #define bootsplash_enable() diff --git a/recipes-kernel/linux/linux-pinephonepro/0006-drm-rockchip-dw-mipi-dsi-Fix-missing-clk_disable_unp.patch b/recipes-kernel/linux/linux-pinephonepro/0006-drm-rockchip-dw-mipi-dsi-Fix-missing-clk_disable_unp.patch new file mode 100644 index 0000000..425c986 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0006-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/recipes-kernel/linux/linux-pinephonepro/0007-bootsplash.patch b/recipes-kernel/linux/linux-pinephonepro/0007-bootsplash.patch new file mode 100644 index 0000000..3f82eb0 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0007-bootsplash.patch @@ -0,0 +1,42 @@ +diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c +index f4166263bb3a..a248429194bb 100644 +--- a/drivers/tty/vt/keyboard.c ++++ b/drivers/tty/vt/keyboard.c +@@ -49,6 +49,8 @@ + + #include + ++#include ++ + /* + * Exported functions/variables + */ +@@ -1413,6 +1415,28 @@ static void kbd_keycode(unsigned int key + } + #endif + ++ /* Trap keys when bootsplash is shown */ ++ if (bootsplash_would_render_now()) { ++ /* Deactivate bootsplash on ESC or Alt+Fxx VT switch */ ++ if (keycode >= KEY_F1 && keycode <= KEY_F12) { ++ bootsplash_disable(); ++ ++ /* ++ * No return here since we want to actually ++ * perform the VT switch. ++ */ ++ } else { ++ if (keycode == KEY_ESC) ++ bootsplash_disable(); ++ ++ /* ++ * Just drop any other keys. ++ * Their effect would be hidden by the splash. ++ */ ++ return; ++ } ++ } ++ + if (kbd->kbdmode == VC_MEDIUMRAW) { + /* + * This is extended medium raw mode, with keys above 127 diff --git a/recipes-kernel/linux/linux-pinephonepro/0007-drm-bridge-dw-mipi-dsi-Fix-enable-disable-of-dsi-con.patch b/recipes-kernel/linux/linux-pinephonepro/0007-drm-bridge-dw-mipi-dsi-Fix-enable-disable-of-dsi-con.patch new file mode 100644 index 0000000..e2eddd2 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0007-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/recipes-kernel/linux/linux-pinephonepro/0008-bootsplash.patch b/recipes-kernel/linux/linux-pinephonepro/0008-bootsplash.patch new file mode 100644 index 0000000..8a3b715 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0008-bootsplash.patch @@ -0,0 +1,21 @@ +diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c +index 3ffc1ce29023..bc6a24c9dfa8 100644 +--- a/drivers/tty/sysrq.c ++++ b/drivers/tty/sysrq.c +@@ -49,6 +49,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -104,6 +105,8 @@ static void sysrq_handle_SAK(int key) + { + struct work_struct *SAK_work = &vc_cons[fg_console].SAK_work; + schedule_work(SAK_work); ++ ++ bootsplash_disable(); + } + static struct sysrq_key_op sysrq_SAK_op = { + .handler = sysrq_handle_SAK, diff --git a/recipes-kernel/linux/linux-pinephonepro/0008-drm-dw-mipi-dsi-rockchip-Never-allow-lane-bandwidth-.patch b/recipes-kernel/linux/linux-pinephonepro/0008-drm-dw-mipi-dsi-rockchip-Never-allow-lane-bandwidth-.patch new file mode 100644 index 0000000..003fd7c --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0008-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/recipes-kernel/linux/linux-pinephonepro/0009-bootsplash.patch b/recipes-kernel/linux/linux-pinephonepro/0009-bootsplash.patch new file mode 100644 index 0000000..add68e7 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0009-bootsplash.patch @@ -0,0 +1,21 @@ +diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c +index 9a39a6fcfe98..8a9c67e1c5d8 100644 +--- a/drivers/video/fbdev/core/fbcon.c ++++ b/drivers/video/fbdev/core/fbcon.c +@@ -1343,6 +1343,16 @@ static void fbcon_cursor(struct vc_data *vc, int mode) + int y; + int c = scr_readw((u16 *) vc->vc_pos); + ++ /* ++ * Disable the splash here so we don't have to hook into ++ * vt_console_print() in drivers/tty/vt/vt.c ++ * ++ * We'd disable the splash just before the call to ++ * hide_cursor() anyway, so this spot is just fine. ++ */ ++ if (oops_in_progress) ++ bootsplash_disable(); ++ + ops->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms); + + if (fbcon_is_inactive(vc, info) || vc->vc_deccm != 1) diff --git a/recipes-kernel/linux/linux-pinephonepro/0009-drm-rockchip-cdn-dp-Disable-CDN-DP-on-disconnect.patch b/recipes-kernel/linux/linux-pinephonepro/0009-drm-rockchip-cdn-dp-Disable-CDN-DP-on-disconnect.patch new file mode 100644 index 0000000..187e5e2 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0009-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/recipes-kernel/linux/linux-pinephonepro/0010-bootsplash.patch b/recipes-kernel/linux/linux-pinephonepro/0010-bootsplash.patch new file mode 100644 index 0000000..e5c1fd0 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0010-bootsplash.patch @@ -0,0 +1,321 @@ +diff --git a/Documentation/ABI/testing/sysfs-platform-bootsplash b/Documentation/ABI/testing/sysfs-platform-bootsplash +new file mode 100644 +index 000000000000..742c7b035ded +--- /dev/null ++++ b/Documentation/ABI/testing/sysfs-platform-bootsplash +@@ -0,0 +1,11 @@ ++What: /sys/devices/platform/bootsplash.0/enabled ++Date: Oct 2017 ++KernelVersion: 4.14 ++Contact: Max Staudt ++Description: ++ Can be set and read. ++ ++ 0: Splash is disabled. ++ 1: Splash is shown whenever fbcon would show a text console ++ (i.e. no graphical application is running), and a splash ++ file is loaded. +diff --git a/Documentation/bootsplash.rst b/Documentation/bootsplash.rst +new file mode 100644 +index 000000000000..611f0c558925 +--- /dev/null ++++ b/Documentation/bootsplash.rst +@@ -0,0 +1,285 @@ ++==================== ++The Linux bootsplash ++==================== ++ ++:Date: November, 2017 ++:Author: Max Staudt ++ ++ ++The Linux bootsplash is a graphical replacement for the '``quiet``' boot ++option, typically showing a logo and a spinner animation as the system starts. ++ ++Currently, it is a part of the Framebuffer Console support, and can be found ++as ``CONFIG_BOOTSPLASH`` in the kernel configuration. This means that as long ++as it is enabled, it hijacks fbcon's output and draws a splash screen instead. ++ ++Purely compiling in the bootsplash will not render it functional - to actually ++render a splash, you will also need a splash theme file. See the example ++utility and script in ``tools/bootsplash`` for a live demo. ++ ++ ++ ++Motivation ++========== ++ ++- The '``quiet``' boot option only suppresses most messages during boot, but ++ errors are still shown. ++ ++- A user space implementation can only show a logo once user space has been ++ initialized far enough to allow this. A kernel splash can display a splash ++ immediately as soon as fbcon can be displayed. ++ ++- Implementing a splash screen in user space (e.g. Plymouth) is problematic ++ due to resource conflicts. ++ ++ For example, if Plymouth is keeping ``/dev/fb0`` (provided via vesafb/efifb) ++ open, then most DRM drivers can't replace it because the address space is ++ still busy - thus leading to a VRAM reservation error. ++ ++ See: https://bugzilla.opensuse.org/show_bug.cgi?id=980750 ++ ++ ++ ++Command line arguments ++====================== ++ ++``bootsplash.bootfile`` ++ Which file in the initramfs to load. ++ ++ The splash theme is loaded via request_firmware(), thus to load ++ ``/lib/firmware/bootsplash/mytheme`` pass the command line: ++ ++ ``bootsplash.bootfile=bootsplash/mytheme`` ++ ++ Note: The splash file *has to be* in the initramfs, as it needs to be ++ available when the splash is initialized early on. ++ ++ Default: none, i.e. a non-functional splash, falling back to showing text. ++ ++ ++ ++sysfs run-time configuration ++============================ ++ ++``/sys/devices/platform/bootsplash.0/enabled`` ++ Enable/disable the bootsplash. ++ The system boots with this set to 1, but will not show a splash unless ++ a splash theme file is also loaded. ++ ++ ++ ++Kconfig ++======= ++ ++``BOOTSPLASH`` ++ Whether to compile in bootsplash support ++ (depends on fbcon compiled in, i.e. ``FRAMEBUFFER_CONSOLE=y``) ++ ++ ++ ++Bootsplash file format ++====================== ++ ++A file specified in the kernel configuration as ``CONFIG_BOOTSPLASH_FILE`` ++or specified on the command line as ``bootsplash.bootfile`` will be loaded ++and displayed as soon as fbcon is initialized. ++ ++ ++Main blocks ++----------- ++ ++There are 3 main blocks in each file: ++ ++ - one File header ++ - n Picture headers ++ - m (Blob header + payload) blocks ++ ++ ++Structures ++---------- ++ ++The on-disk structures are defined in ++``drivers/video/fbdev/core/bootsplash_file.h`` and represent these blocks: ++ ++ - ``struct splash_file_header`` ++ ++ Represents the file header, with splash-wide information including: ++ ++ - The magic string "``Linux bootsplash``" on big-endian platforms ++ (the reverse on little endian) ++ - The file format version (for incompatible updates, hopefully never) ++ - The background color ++ - Number of picture and blob blocks ++ - Animation speed (we only allow one delay for all animations) ++ ++ The file header is followed by the first picture header. ++ ++ ++ - ``struct splash_picture_header`` ++ ++ Represents an object (picture) drawn on screen, including its immutable ++ properties: ++ - Width, height ++ - Positioning relative to screen corners or in the center ++ - Animation, if any ++ - Animation type ++ - Number of blobs ++ ++ The picture header is followed by another picture header, up until n ++ picture headers (as defined in the file header) have been read. Then, ++ the (blob header, payload) pairs follow. ++ ++ ++ - ``struct splash_blob_header`` ++ (followed by payload) ++ ++ Represents one raw data stream. So far, only picture data is defined. ++ ++ The blob header is followed by a payload, then padding to n*16 bytes, ++ then (if further blobs are defined in the file header) a further blob ++ header. ++ ++ ++Alignment ++--------- ++ ++The bootsplash file is designed to be loaded into memory as-is. ++ ++All structures are a multiple of 16 bytes long, all elements therein are ++aligned to multiples of their length, and the payloads are always padded ++up to multiples of 16 bytes. This is to allow aligned accesses in all ++cases while still simply mapping the structures over an in-memory copy of ++the bootsplash file. ++ ++ ++Further information ++------------------- ++ ++Please see ``drivers/video/fbdev/core/bootsplash_file.h`` for further ++details and possible values in the file. ++ ++ ++ ++Hooks - how the bootsplash is integrated ++======================================== ++ ++``drivers/video/fbdev/core/fbcon.c`` ++ ``fbcon_init()`` calls ``bootsplash_init()``, which loads the default ++ bootsplash file or the one specified on the kernel command line. ++ ++ ``fbcon_switch()`` draws the bootsplash when it's active, and is also ++ one of the callers of ``set_blitting_type()``. ++ ++ ``set_blitting_type()`` calls ``fbcon_set_dummyops()`` when the ++ bootsplash is active, overriding the text rendering functions. ++ ++ ``fbcon_cursor()`` will call ``bootsplash_disable()`` when an oops is ++ being printed in order to make a kernel panic visible. ++ ++``drivers/video/fbdev/core/dummyblit.c`` ++ This contains the dummy text rendering functions used to suppress text ++ output while the bootsplash is shown. ++ ++``drivers/tty/vt/keyboard.c`` ++ ``kbd_keycode()`` can call ``bootsplash_disable()`` when the user ++ presses ESC or F1-F12 (changing VT). This is to provide a built-in way ++ of disabling the splash manually at any time. ++ ++ ++ ++FAQ: Frequently Asked Questions ++=============================== ++ ++I want to see the log! How do I show the log? ++--------------------------------------------- ++ ++Press ESC while the splash is shown, or remove the ``bootsplash.bootfile`` ++parameter from the kernel cmdline. Without that parameter, the bootsplash ++will boot disabled. ++ ++ ++Why use FB instead of modern DRM/KMS? ++------------------------------------- ++ ++This is a semantic problem: ++ - What memory to draw the splash to? ++ - And what mode will the screen be set to? ++ ++Using the fbdev emulation solves these issues. ++ ++Let's start from a bare KMS system, without fbcon, and without fbdev ++emulation. In this case, as long as userspace doesn't open the KMS ++device, the state of the screen is undefined. No framebuffer is ++allocated in video RAM, and no particular mode is set. ++ ++In this case, we'd have to allocate a framebuffer to show the splash, ++and set our mode ourselves. This either wastes a screenful of video RAM ++if the splash is to co-exist with the userspace program's own allocated ++framebuffer, or there is a flicker as we deactivate and delete the ++bootsplash's framebuffer and hand control over to userspace. Since we ++may set a different mode than userspace, we'd also have flicker due ++to mode switching. ++ ++This logic is already contained in every KMS driver that performs fbdev ++emulation. So we might as well use that. And the correct API to do so is ++fbdev. Plus, we get compatibility with old, pure fbdev drivers for free. ++With the fbdev emulation, there is *always* a well-defined framebuffer ++to draw on. And the selection of mode has already been done by the ++graphics driver, so we don't need to reinvent that wheel, either. ++Finally, if userspace decides to use /dev/fbX, we don't have to worry ++about wasting video RAM, either. ++ ++ ++Why is the bootsplash integrated in fbcon? ++------------------------------------------ ++ ++Right now, the bootsplash is drawn from within fbcon, as this allows us ++to easily know *when* to draw - i.e. when we're safe from fbcon and ++userspace drawing all over our beautiful splash logo. ++ ++Separating them is not easy - see the to-do list below. ++ ++ ++ ++TO DO list for future development ++================================= ++ ++Second enable/disable switch for the system ++------------------------------------------- ++ ++It may be helpful to differentiate between the system and the user ++switching off the bootsplash. Thus, the system may make it disappear and ++reappear e.g. for a password prompt, yet once the user has pressed ESC, ++it could stay gone. ++ ++ ++Fix buggy DRM/KMS drivers ++------------------------- ++ ++Currently, the splash code manually checks for fbdev emulation provided by ++the ast, cirrus, and mgag200 DRM/KMS drivers. ++These drivers use a manual mechanism similar to deferred I/O for their FB ++emulation, and thus need to be manually flushed onto the screen in the same ++way. ++ ++This may be improved upon in several ways: ++ ++1. Changing these drivers to expose the fbdev BO's memory directly, like ++ bochsdrmfb does. ++2. Creating a new fb_ops->fb_flush() API to allow the kernel to flush the ++ framebuffer once the bootsplash has been drawn into it. ++ ++ ++Separating from fbcon ++--------------------- ++ ++Separating these two components would yield independence from fbcon being ++compiled into the kernel, and thus lowering code size in embedded ++applications. ++ ++To do this cleanly will involve a clean separation of users of an FB device ++within the kernel, i.e. fbcon, bootsplash, and userspace. Right now, the ++legacy fbcon code and VT code co-operate to switch between fbcon and ++userspace (by setting the VT into KD_GRAPHICS mode). Installing a muxer ++between these components ensues refactoring of old code and checking for ++correct locking. +diff --git a/MAINTAINERS b/MAINTAINERS +index 5c237445761e..7ffac272434e 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -2709,6 +2709,8 @@ BOOTSPLASH + M: Max Staudt + L: linux-fbdev@vger.kernel.org + S: Maintained ++F: Documentation/ABI/testing/sysfs-platform-bootsplash ++F: Documentation/bootsplash.rst + F: drivers/video/fbdev/core/bootsplash*.* + F: drivers/video/fbdev/core/dummycon.c + F: include/linux/bootsplash.h diff --git a/recipes-kernel/linux/linux-pinephonepro/0010-video-fbdev-Add-events-for-early-fb-event-support.patch b/recipes-kernel/linux/linux-pinephonepro/0010-video-fbdev-Add-events-for-early-fb-event-support.patch new file mode 100644 index 0000000..d424c70 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0010-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 826175a..fb1651a 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 6f3db99..ffef502a 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/recipes-kernel/linux/linux-pinephonepro/0011-bootsplash.patch b/recipes-kernel/linux/linux-pinephonepro/0011-bootsplash.patch new file mode 100644 index 0000000..8e87eb4 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0011-bootsplash.patch @@ -0,0 +1,129 @@ +diff --git a/Documentation/ABI/testing/sysfs-platform-bootsplash b/Documentation/ABI/testing/sysfs-platform-bootsplash +index 742c7b035ded..f8f4b259220e 100644 +--- a/Documentation/ABI/testing/sysfs-platform-bootsplash ++++ b/Documentation/ABI/testing/sysfs-platform-bootsplash +@@ -9,3 +9,35 @@ Description: + 1: Splash is shown whenever fbcon would show a text console + (i.e. no graphical application is running), and a splash + file is loaded. ++ ++What: /sys/devices/platform/bootsplash.0/drop_splash ++Date: Oct 2017 ++KernelVersion: 4.14 ++Contact: Max Staudt ++Description: ++ Can only be set. ++ ++ Any value written will cause the current splash theme file ++ to be unloaded and the text console to be redrawn. ++ ++What: /sys/devices/platform/bootsplash.0/load_file ++Date: Oct 2017 ++KernelVersion: 4.14 ++Contact: Max Staudt ++Description: ++ Can only be set. ++ ++ Any value written will cause the splash to be disabled and ++ internal memory structures to be freed. ++ ++ A firmware path written will cause a new theme file to be ++ loaded and the current bootsplash to be replaced. ++ The current enabled/disabled status is not touched. ++ If the splash is already active, it will be redrawn. ++ ++ The path has to be a path in /lib/firmware since ++ request_firmware() is used to fetch the data. ++ ++ When setting the splash from the shell, echo -n has to be ++ used as any trailing '\n' newline will be interpreted as ++ part of the path. +diff --git a/Documentation/bootsplash.rst b/Documentation/bootsplash.rst +index 611f0c558925..b35aba5093e8 100644 +--- a/Documentation/bootsplash.rst ++++ b/Documentation/bootsplash.rst +@@ -67,6 +67,14 @@ sysfs run-time configuration + a splash theme file is also loaded. + + ++``/sys/devices/platform/bootsplash.0/drop_splash`` ++ Unload splash data and free memory. ++ ++``/sys/devices/platform/bootsplash.0/load_file`` ++ Load a splash file from ``/lib/firmware/``. ++ Note that trailing newlines will be interpreted as part of the file name. ++ ++ + + Kconfig + ======= +diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c +index 13fcaabbc2ca..16cb0493629d 100644 +--- a/drivers/video/fbdev/core/bootsplash.c ++++ b/drivers/video/fbdev/core/bootsplash.c +@@ -251,11 +251,65 @@ static ssize_t splash_store_enabled(struct device *device, + return count; + } + ++static ssize_t splash_store_drop_splash(struct device *device, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct splash_file_priv *fp; ++ ++ if (!buf || !count || !splash_state.file) ++ return count; ++ ++ mutex_lock(&splash_state.data_lock); ++ fp = splash_state.file; ++ splash_state.file = NULL; ++ mutex_unlock(&splash_state.data_lock); ++ ++ /* Redraw the text console */ ++ schedule_work(&splash_state.work_redraw_vc); ++ ++ bootsplash_free_file(fp); ++ ++ return count; ++} ++ ++static ssize_t splash_store_load_file(struct device *device, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct splash_file_priv *fp, *fp_old; ++ ++ if (!count) ++ return 0; ++ ++ fp = bootsplash_load_firmware(&splash_state.splash_device->dev, ++ buf); ++ ++ if (!fp) ++ return -ENXIO; ++ ++ mutex_lock(&splash_state.data_lock); ++ fp_old = splash_state.file; ++ splash_state.splash_fb = NULL; ++ splash_state.file = fp; ++ mutex_unlock(&splash_state.data_lock); ++ ++ /* Update the splash or text console */ ++ schedule_work(&splash_state.work_redraw_vc); ++ ++ bootsplash_free_file(fp_old); ++ return count; ++} ++ + static DEVICE_ATTR(enabled, 0644, splash_show_enabled, splash_store_enabled); ++static DEVICE_ATTR(drop_splash, 0200, NULL, splash_store_drop_splash); ++static DEVICE_ATTR(load_file, 0200, NULL, splash_store_load_file); + + + static struct attribute *splash_dev_attrs[] = { + &dev_attr_enabled.attr, ++ &dev_attr_drop_splash.attr, ++ &dev_attr_load_file.attr, + NULL + }; + diff --git a/recipes-kernel/linux/linux-pinephonepro/0011-power-rk818-Configure-rk808-clkout2-function.patch b/recipes-kernel/linux/linux-pinephonepro/0011-power-rk818-Configure-rk808-clkout2-function.patch new file mode 100644 index 0000000..8ff73f2 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0011-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/recipes-kernel/linux/linux-pinephonepro/0012-bootsplash.patch b/recipes-kernel/linux/linux-pinephonepro/0012-bootsplash.patch new file mode 100644 index 0000000..5d8ea1f --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0012-bootsplash.patch @@ -0,0 +1,511 @@ +diff --git a/MAINTAINERS b/MAINTAINERS +index 7ffac272434e..ddff07cd794c 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -2715,6 +2715,7 @@ F: drivers/video/fbdev/core/bootsplash*.* + F: drivers/video/fbdev/core/dummycon.c + F: include/linux/bootsplash.h + F: include/uapi/linux/bootsplash_file.h ++F: tools/bootsplash/* + + BPF (Safe dynamic programs and tools) + M: Alexei Starovoitov +diff --git a/tools/bootsplash/.gitignore b/tools/bootsplash/.gitignore +new file mode 100644 +index 000000000000..091b99a17567 +--- /dev/null ++++ b/tools/bootsplash/.gitignore +@@ -0,0 +1 @@ ++bootsplash-packer +diff --git a/tools/bootsplash/Makefile b/tools/bootsplash/Makefile +new file mode 100644 +index 000000000000..0ad8e8a84942 +--- /dev/null ++++ b/tools/bootsplash/Makefile +@@ -0,0 +1,9 @@ ++CC := $(CROSS_COMPILE)gcc ++CFLAGS := -I../../usr/include ++ ++PROGS := bootsplash-packer ++ ++all: $(PROGS) ++ ++clean: ++ rm -fr $(PROGS) +diff --git a/tools/bootsplash/bootsplash-packer.c b/tools/bootsplash/bootsplash-packer.c +new file mode 100644 +index 000000000000..ffb6a8b69885 +--- /dev/null ++++ b/tools/bootsplash/bootsplash-packer.c +@@ -0,0 +1,471 @@ ++/* ++ * Kernel based bootsplash. ++ * ++ * (Splash file packer tool) ++ * ++ * Authors: ++ * Max Staudt ++ * ++ * SPDX-License-Identifier: GPL-2.0 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++ ++static void print_help(char *progname) ++{ ++ printf("Usage: %s [OPTIONS] outfile\n", progname); ++ printf("\n" ++ "Options, executed in order given:\n" ++ " -h, --help Print this help message\n" ++ "\n" ++ " --bg_red Background color (red part)\n" ++ " --bg_green Background color (green part)\n" ++ " --bg_blue Background color (blue part)\n" ++ " --bg_reserved (do not use)\n" ++ " --frame_ms Minimum milliseconds between animation steps\n" ++ "\n" ++ " --picture Start describing the next picture\n" ++ " --pic_width Picture width in pixels\n" ++ " --pic_height Picture height in pixels\n" ++ " --pic_position Coarse picture placement:\n" ++ " 0x00 - Top left\n" ++ " 0x01 - Top\n" ++ " 0x02 - Top right\n" ++ " 0x03 - Right\n" ++ " 0x04 - Bottom right\n" ++ " 0x05 - Bottom\n" ++ " 0x06 - Bottom left\n" ++ " 0x07 - Left\n" ++ "\n" ++ " Flags:\n" ++ " 0x10 - Calculate offset from corner towards center,\n" ++ " rather than from center towards corner\n" ++ " --pic_position_offset Distance from base position in pixels\n" ++ " --pic_anim_type Animation type:\n" ++ " 0 - None\n" ++ " 1 - Forward loop\n" ++ " --pic_anim_loop Loop point for animation\n" ++ "\n" ++ " --blob Include next data stream\n" ++ " --blob_type Type of data\n" ++ " --blob_picture_id Picture to associate this blob with, starting at 0\n" ++ " (default: number of last --picture)\n" ++ "\n"); ++ printf("This tool will write %s files.\n\n", ++#if __BYTE_ORDER == __BIG_ENDIAN ++ "Big Endian (BE)"); ++#elif __BYTE_ORDER == __LITTLE_ENDIAN ++ "Little Endian (LE)"); ++#else ++#error ++#endif ++} ++ ++ ++struct blob_entry { ++ struct blob_entry *next; ++ ++ char *fn; ++ ++ struct splash_blob_header header; ++}; ++ ++ ++static void dump_file_header(struct splash_file_header *h) ++{ ++ printf(" --- File header ---\n"); ++ printf("\n"); ++ printf(" version: %5u\n", h->version); ++ printf("\n"); ++ printf(" bg_red: %5u\n", h->bg_red); ++ printf(" bg_green: %5u\n", h->bg_green); ++ printf(" bg_blue: %5u\n", h->bg_blue); ++ printf(" bg_reserved: %5u\n", h->bg_reserved); ++ printf("\n"); ++ printf(" num_blobs: %5u\n", h->num_blobs); ++ printf(" num_pics: %5u\n", h->num_pics); ++ printf("\n"); ++ printf(" frame_ms: %5u\n", h->frame_ms); ++ printf("\n"); ++} ++ ++static void dump_pic_header(struct splash_pic_header *ph) ++{ ++ printf(" --- Picture header ---\n"); ++ printf("\n"); ++ printf(" width: %5u\n", ph->width); ++ printf(" height: %5u\n", ph->height); ++ printf("\n"); ++ printf(" num_blobs: %5u\n", ph->num_blobs); ++ printf("\n"); ++ printf(" position: %0x3x\n", ph->position); ++ printf(" position_offset: %5u\n", ph->position_offset); ++ printf("\n"); ++ printf(" anim_type: %5u\n", ph->anim_type); ++ printf(" anim_loop: %5u\n", ph->anim_loop); ++ printf("\n"); ++} ++ ++static void dump_blob(struct blob_entry *b) ++{ ++ printf(" --- Blob header ---\n"); ++ printf("\n"); ++ printf(" length: %7u\n", b->header.length); ++ printf(" type: %7u\n", b->header.type); ++ printf("\n"); ++ printf(" picture_id: %7u\n", b->header.picture_id); ++ printf("\n"); ++} ++ ++ ++#define OPT_MAX(var, max) \ ++ do { \ ++ if ((var) > max) { \ ++ fprintf(stderr, "--%s: Invalid value\n", \ ++ long_options[option_index].name); \ ++ break; \ ++ } \ ++ } while (0) ++ ++static struct option long_options[] = { ++ {"help", 0, 0, 'h'}, ++ {"bg_red", 1, 0, 10001}, ++ {"bg_green", 1, 0, 10002}, ++ {"bg_blue", 1, 0, 10003}, ++ {"bg_reserved", 1, 0, 10004}, ++ {"frame_ms", 1, 0, 10005}, ++ {"picture", 0, 0, 20000}, ++ {"pic_width", 1, 0, 20001}, ++ {"pic_height", 1, 0, 20002}, ++ {"pic_position", 1, 0, 20003}, ++ {"pic_position_offset", 1, 0, 20004}, ++ {"pic_anim_type", 1, 0, 20005}, ++ {"pic_anim_loop", 1, 0, 20006}, ++ {"blob", 1, 0, 30000}, ++ {"blob_type", 1, 0, 30001}, ++ {"blob_picture_id", 1, 0, 30002}, ++ {NULL, 0, NULL, 0} ++}; ++ ++ ++int main(int argc, char **argv) ++{ ++ FILE *of; ++ char *ofn; ++ int c; ++ int option_index = 0; ++ ++ unsigned long ul; ++ struct splash_file_header fh = {}; ++ struct splash_pic_header ph[255]; ++ struct blob_entry *blob_first = NULL; ++ struct blob_entry *blob_last = NULL; ++ struct blob_entry *blob_cur = NULL; ++ ++ if (argc < 2) { ++ print_help(argv[0]); ++ return EXIT_FAILURE; ++ } ++ ++ ++ /* Parse and and execute user commands */ ++ while ((c = getopt_long(argc, argv, "h", ++ long_options, &option_index)) != -1) { ++ switch (c) { ++ case 10001: /* bg_red */ ++ ul = strtoul(optarg, NULL, 0); ++ OPT_MAX(ul, 255); ++ fh.bg_red = ul; ++ break; ++ case 10002: /* bg_green */ ++ ul = strtoul(optarg, NULL, 0); ++ OPT_MAX(ul, 255); ++ fh.bg_green = ul; ++ break; ++ case 10003: /* bg_blue */ ++ ul = strtoul(optarg, NULL, 0); ++ OPT_MAX(ul, 255); ++ fh.bg_blue = ul; ++ break; ++ case 10004: /* bg_reserved */ ++ ul = strtoul(optarg, NULL, 0); ++ OPT_MAX(ul, 255); ++ fh.bg_reserved = ul; ++ break; ++ case 10005: /* frame_ms */ ++ ul = strtoul(optarg, NULL, 0); ++ OPT_MAX(ul, 65535); ++ fh.frame_ms = ul; ++ break; ++ ++ ++ case 20000: /* picture */ ++ if (fh.num_pics >= 255) { ++ fprintf(stderr, "--%s: Picture array full\n", ++ long_options[option_index].name); ++ break; ++ } ++ ++ fh.num_pics++; ++ break; ++ ++ case 20001: /* pic_width */ ++ ul = strtoul(optarg, NULL, 0); ++ OPT_MAX(ul, 65535); ++ ph[fh.num_pics - 1].width = ul; ++ break; ++ ++ case 20002: /* pic_height */ ++ ul = strtoul(optarg, NULL, 0); ++ OPT_MAX(ul, 65535); ++ ph[fh.num_pics - 1].height = ul; ++ break; ++ ++ case 20003: /* pic_position */ ++ ul = strtoul(optarg, NULL, 0); ++ OPT_MAX(ul, 255); ++ ph[fh.num_pics - 1].position = ul; ++ break; ++ ++ case 20004: /* pic_position_offset */ ++ ul = strtoul(optarg, NULL, 0); ++ OPT_MAX(ul, 255); ++ ph[fh.num_pics - 1].position_offset = ul; ++ break; ++ ++ case 20005: /* pic_anim_type */ ++ ul = strtoul(optarg, NULL, 0); ++ OPT_MAX(ul, 255); ++ ph[fh.num_pics - 1].anim_type = ul; ++ break; ++ ++ case 20006: /* pic_anim_loop */ ++ ul = strtoul(optarg, NULL, 0); ++ OPT_MAX(ul, 255); ++ ph[fh.num_pics - 1].anim_loop = ul; ++ break; ++ ++ ++ case 30000: /* blob */ ++ if (fh.num_blobs >= 65535) { ++ fprintf(stderr, "--%s: Blob array full\n", ++ long_options[option_index].name); ++ break; ++ } ++ ++ blob_cur = calloc(1, sizeof(struct blob_entry)); ++ if (!blob_cur) { ++ fprintf(stderr, "--%s: Out of memory\n", ++ long_options[option_index].name); ++ break; ++ } ++ ++ blob_cur->fn = optarg; ++ if (fh.num_pics) ++ blob_cur->header.picture_id = fh.num_pics - 1; ++ ++ if (!blob_first) ++ blob_first = blob_cur; ++ if (blob_last) ++ blob_last->next = blob_cur; ++ blob_last = blob_cur; ++ fh.num_blobs++; ++ break; ++ ++ case 30001: /* blob_type */ ++ if (!blob_cur) { ++ fprintf(stderr, "--%s: No blob selected\n", ++ long_options[option_index].name); ++ break; ++ } ++ ++ ul = strtoul(optarg, NULL, 0); ++ OPT_MAX(ul, 255); ++ blob_cur->header.type = ul; ++ break; ++ ++ case 30002: /* blob_picture_id */ ++ if (!blob_cur) { ++ fprintf(stderr, "--%s: No blob selected\n", ++ long_options[option_index].name); ++ break; ++ } ++ ++ ul = strtoul(optarg, NULL, 0); ++ OPT_MAX(ul, 255); ++ blob_cur->header.picture_id = ul; ++ break; ++ ++ ++ ++ case 'h': ++ case '?': ++ default: ++ print_help(argv[0]); ++ goto EXIT; ++ } /* switch (c) */ ++ } /* while ((c = getopt_long(...)) != -1) */ ++ ++ /* Consume and drop lone arguments */ ++ while (optind < argc) { ++ ofn = argv[optind]; ++ optind++; ++ } ++ ++ ++ /* Read file lengths */ ++ for (blob_cur = blob_first; blob_cur; blob_cur = blob_cur->next) { ++ FILE *f; ++ long pos; ++ int i; ++ ++ if (!blob_cur->fn) ++ continue; ++ ++ f = fopen(blob_cur->fn, "rb"); ++ if (!f) ++ goto ERR_FILE_LEN; ++ ++ if (fseek(f, 0, SEEK_END)) ++ goto ERR_FILE_LEN; ++ ++ pos = ftell(f); ++ if (pos < 0 || pos > (1 << 30)) ++ goto ERR_FILE_LEN; ++ ++ blob_cur->header.length = pos; ++ ++ fclose(f); ++ continue; ++ ++ERR_FILE_LEN: ++ fprintf(stderr, "Error getting file length (or too long): %s\n", ++ blob_cur->fn); ++ if (f) ++ fclose(f); ++ continue; ++ } ++ ++ ++ /* Set magic headers */ ++#if __BYTE_ORDER == __BIG_ENDIAN ++ memcpy(&fh.id[0], BOOTSPLASH_MAGIC_BE, 16); ++#elif __BYTE_ORDER == __LITTLE_ENDIAN ++ memcpy(&fh.id[0], BOOTSPLASH_MAGIC_LE, 16); ++#else ++#error ++#endif ++ fh.version = BOOTSPLASH_VERSION; ++ ++ /* Set blob counts */ ++ for (blob_cur = blob_first; blob_cur; blob_cur = blob_cur->next) { ++ if (blob_cur->header.picture_id < fh.num_pics) ++ ph[blob_cur->header.picture_id].num_blobs++; ++ } ++ ++ ++ /* Dump structs */ ++ dump_file_header(&fh); ++ ++ for (ul = 0; ul < fh.num_pics; ul++) ++ dump_pic_header(&ph[ul]); ++ ++ for (blob_cur = blob_first; blob_cur; blob_cur = blob_cur->next) ++ dump_blob(blob_cur); ++ ++ ++ /* Write to file */ ++ printf("Writing splash to file: %s\n", ofn); ++ of = fopen(ofn, "wb"); ++ if (!of) ++ goto ERR_WRITING; ++ ++ if (fwrite(&fh, sizeof(struct splash_file_header), 1, of) != 1) ++ goto ERR_WRITING; ++ ++ for (ul = 0; ul < fh.num_pics; ul++) { ++ if (fwrite(&ph[ul], sizeof(struct splash_pic_header), 1, of) ++ != 1) ++ goto ERR_WRITING; ++ } ++ ++ blob_cur = blob_first; ++ while (blob_cur) { ++ struct blob_entry *blob_old = blob_cur; ++ FILE *f; ++ char *buf[256]; ++ uint32_t left; ++ ++ if (fwrite(&blob_cur->header, ++ sizeof(struct splash_blob_header), 1, of) != 1) ++ goto ERR_WRITING; ++ ++ if (!blob_cur->header.length || !blob_cur->fn) ++ continue; ++ ++ f = fopen(blob_cur->fn, "rb"); ++ if (!f) ++ goto ERR_FILE_COPY; ++ ++ left = blob_cur->header.length; ++ while (left >= sizeof(buf)) { ++ if (fread(buf, sizeof(buf), 1, f) != 1) ++ goto ERR_FILE_COPY; ++ if (fwrite(buf, sizeof(buf), 1, of) != 1) ++ goto ERR_FILE_COPY; ++ left -= sizeof(buf); ++ } ++ if (left) { ++ if (fread(buf, left, 1, f) != 1) ++ goto ERR_FILE_COPY; ++ if (fwrite(buf, left, 1, of) != 1) ++ goto ERR_FILE_COPY; ++ } ++ ++ /* Pad data stream to 16 bytes */ ++ if (left % 16) { ++ if (fwrite("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", ++ 16 - (left % 16), 1, of) != 1) ++ goto ERR_FILE_COPY; ++ } ++ ++ fclose(f); ++ blob_cur = blob_cur->next; ++ free(blob_old); ++ continue; ++ ++ERR_FILE_COPY: ++ if (f) ++ fclose(f); ++ goto ERR_WRITING; ++ } ++ ++ fclose(of); ++ ++EXIT: ++ return EXIT_SUCCESS; ++ ++ ++ERR_WRITING: ++ fprintf(stderr, "Error writing splash.\n"); ++ fprintf(stderr, "The output file is probably corrupt.\n"); ++ if (of) ++ fclose(of); ++ ++ while (blob_cur) { ++ struct blob_entry *blob_old = blob_cur; ++ ++ blob_cur = blob_cur->next; ++ free(blob_old); ++ } ++ ++ return EXIT_FAILURE; ++} diff --git a/recipes-kernel/linux/linux-pinephonepro/0012-power-rk818-battery-Add-battery-driver-for-RK818.patch b/recipes-kernel/linux/linux-pinephonepro/0012-power-rk818-battery-Add-battery-driver-for-RK818.patch new file mode 100644 index 0000000..fd0eb7e --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0012-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/recipes-kernel/linux/linux-pinephonepro/0013-power-supply-rk818-battery-Use-a-more-propper-compat.patch b/recipes-kernel/linux/linux-pinephonepro/0013-power-supply-rk818-battery-Use-a-more-propper-compat.patch new file mode 100644 index 0000000..fd97202 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0013-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/recipes-kernel/linux/linux-pinephonepro/0014-power-supply-core-Don-t-ignore-max_current-of-0-when.patch b/recipes-kernel/linux/linux-pinephonepro/0014-power-supply-core-Don-t-ignore-max_current-of-0-when.patch new file mode 100644 index 0000000..ce6a96f --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0014-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 fc12a4f..f681372 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/recipes-kernel/linux/linux-pinephonepro/0015-power-supply-rk818-charger-Implement-charger-driver-.patch b/recipes-kernel/linux/linux-pinephonepro/0015-power-supply-rk818-charger-Implement-charger-driver-.patch new file mode 100644 index 0000000..37ea4fc --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0015-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..7b67a0b +--- /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_BATTERY, ++ .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/recipes-kernel/linux/linux-pinephonepro/0016-usb-typec-fusb302-Set-the-current-before-enabling-pu.patch b/recipes-kernel/linux/linux-pinephonepro/0016-usb-typec-fusb302-Set-the-current-before-enabling-pu.patch new file mode 100644 index 0000000..f42aed2 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0016-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/recipes-kernel/linux/linux-pinephonepro/0017-usb-typec-fusb302-Extend-debugging-interface-with-dr.patch b/recipes-kernel/linux/linux-pinephonepro/0017-usb-typec-fusb302-Extend-debugging-interface-with-dr.patch new file mode 100644 index 0000000..0ab3229 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0017-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/recipes-kernel/linux/linux-pinephonepro/0018-usb-typec-fusb302-Retry-reading-of-CC-pins-status-if.patch b/recipes-kernel/linux/linux-pinephonepro/0018-usb-typec-fusb302-Retry-reading-of-CC-pins-status-if.patch new file mode 100644 index 0000000..096673e --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0018-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/recipes-kernel/linux/linux-pinephonepro/0019-usb-typec-fusb302-More-useful-of-logging-status-on-i.patch b/recipes-kernel/linux/linux-pinephonepro/0019-usb-typec-fusb302-More-useful-of-logging-status-on-i.patch new file mode 100644 index 0000000..2cec92e --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0019-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/recipes-kernel/linux/linux-pinephonepro/0020-usb-typec-fusb302-Update-VBUS-state-even-if-VBUS-int.patch b/recipes-kernel/linux/linux-pinephonepro/0020-usb-typec-fusb302-Update-VBUS-state-even-if-VBUS-int.patch new file mode 100644 index 0000000..9335999 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0020-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/recipes-kernel/linux/linux-pinephonepro/0021-usb-typec-fusb302-Make-tcpm-fusb302-logs-less-pollut.patch b/recipes-kernel/linux/linux-pinephonepro/0021-usb-typec-fusb302-Make-tcpm-fusb302-logs-less-pollut.patch new file mode 100644 index 0000000..a8d8cad --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0021-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 6010b99..ab69f55 100644 +--- a/drivers/usb/typec/tcpm/tcpm.c ++++ b/drivers/usb/typec/tcpm/tcpm.c +@@ -775,7 +775,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); + } +@@ -868,10 +868,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); +@@ -917,7 +919,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); + } +@@ -950,7 +952,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) +@@ -965,7 +967,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) { +@@ -2870,8 +2872,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); +@@ -5040,7 +5042,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 +@@ -5130,7 +5132,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/recipes-kernel/linux/linux-pinephonepro/0022-usb-typec-fusb302-Add-OF-extcon-support.patch b/recipes-kernel/linux/linux-pinephonepro/0022-usb-typec-fusb302-Add-OF-extcon-support.patch new file mode 100644 index 0000000..64f2ffa --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0022-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/recipes-kernel/linux/linux-pinephonepro/0023-usb-typec-fusb302-Fix-register-definitions.patch b/recipes-kernel/linux/linux-pinephonepro/0023-usb-typec-fusb302-Fix-register-definitions.patch new file mode 100644 index 0000000..f06e87f --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0023-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/recipes-kernel/linux/linux-pinephonepro/0024-usb-typec-fusb302-Clear-interrupts-before-we-start-t.patch b/recipes-kernel/linux/linux-pinephonepro/0024-usb-typec-fusb302-Clear-interrupts-before-we-start-t.patch new file mode 100644 index 0000000..5909161 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0024-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/recipes-kernel/linux/linux-pinephonepro/0025-usb-typec-typec-extcon-Add-typec-extcon-bridge-drive.patch b/recipes-kernel/linux/linux-pinephonepro/0025-usb-typec-typec-extcon-Add-typec-extcon-bridge-drive.patch new file mode 100644 index 0000000..d8c007c --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0025-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/recipes-kernel/linux/linux-pinephonepro/0026-phy-rockchip-typec-Make-sure-the-plug-orientation-is.patch b/recipes-kernel/linux/linux-pinephonepro/0026-phy-rockchip-typec-Make-sure-the-plug-orientation-is.patch new file mode 100644 index 0000000..abb3c5c --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0026-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/recipes-kernel/linux/linux-pinephonepro/0027-media-i2c-imx258-Add-support-for-powerdown-gpio.patch b/recipes-kernel/linux/linux-pinephonepro/0027-media-i2c-imx258-Add-support-for-powerdown-gpio.patch new file mode 100644 index 0000000..6d20e9e --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0027-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/recipes-kernel/linux/linux-pinephonepro/0028-media-i2c-imx258-Don-t-be-too-strict-about-clock-rat.patch b/recipes-kernel/linux/linux-pinephonepro/0028-media-i2c-imx258-Don-t-be-too-strict-about-clock-rat.patch new file mode 100644 index 0000000..ef345ec --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0028-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/recipes-kernel/linux/linux-pinephonepro/0029-media-i2c-imx258-Add-support-for-reset-gpio.patch b/recipes-kernel/linux/linux-pinephonepro/0029-media-i2c-imx258-Add-support-for-reset-gpio.patch new file mode 100644 index 0000000..af85962 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0029-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/recipes-kernel/linux/linux-pinephonepro/0030-media-i2c-imx258-Add-support-for-power-supplies.patch b/recipes-kernel/linux/linux-pinephonepro/0030-media-i2c-imx258-Add-support-for-power-supplies.patch new file mode 100644 index 0000000..23c09d8 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0030-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/recipes-kernel/linux/linux-pinephonepro/0031-media-ov5640-Add-more-framerates-to-the-driver-some-.patch b/recipes-kernel/linux/linux-pinephonepro/0031-media-ov5640-Add-more-framerates-to-the-driver-some-.patch new file mode 100644 index 0000000..5f3faf5 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0031-media-ov5640-Add-more-framerates-to-the-driver-some-.patch @@ -0,0 +1,52 @@ +From: Ondrej Jirman +Date: Fri, 24 Jan 2020 18:25:12 +0100 +Subject: [PATCH 091/194] media: ov5640: Add more framerates to the driver + (some of them even work!) + +Signed-off-by: Ondrej Jirman +--- + drivers/media/i2c/ov5640.c | 14 +++++++++++--- + 1 file changed, 11 insertions(+), 3 deletions(-) + +diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c +index ddbd713..624a3c0 100644 +--- a/drivers/media/i2c/ov5640.c ++++ b/drivers/media/i2c/ov5640.c +@@ -112,7 +112,11 @@ enum ov5640_mode_id { + }; + + enum ov5640_frame_rate { +- OV5640_15_FPS = 0, ++ OV5640_2_FPS = 0, ++ OV5640_3_FPS, ++ OV5640_5_FPS, ++ OV5640_7_FPS, ++ OV5640_15_FPS, + OV5640_30_FPS, + OV5640_60_FPS, + OV5640_NUM_FRAMERATES, +@@ -156,6 +160,10 @@ MODULE_PARM_DESC(virtual_channel, + "MIPI CSI-2 virtual channel (0..3), default 0"); + + static const int ov5640_framerates[] = { ++ [OV5640_2_FPS] = 2, ++ [OV5640_3_FPS] = 3, ++ [OV5640_5_FPS] = 5, ++ [OV5640_7_FPS] = 7, + [OV5640_15_FPS] = 15, + [OV5640_30_FPS] = 30, + [OV5640_60_FPS] = 60, +@@ -2193,11 +2201,11 @@ static int ov5640_try_frame_interval(struct ov5640_dev *sensor, + u32 width, u32 height) + { + const struct ov5640_mode_info *mode; +- enum ov5640_frame_rate rate = OV5640_15_FPS; ++ enum ov5640_frame_rate rate = OV5640_2_FPS; + int minfps, maxfps, best_fps, fps; + int i; + +- minfps = ov5640_framerates[OV5640_15_FPS]; ++ minfps = ov5640_framerates[OV5640_2_FPS]; + maxfps = ov5640_framerates[OV5640_60_FPS]; + + if (fi->numerator == 0) { diff --git a/recipes-kernel/linux/linux-pinephonepro/0032-media-ov5640-Experiment-Try-to-disable-denoising-sha.patch b/recipes-kernel/linux/linux-pinephonepro/0032-media-ov5640-Experiment-Try-to-disable-denoising-sha.patch new file mode 100644 index 0000000..a9b034a --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0032-media-ov5640-Experiment-Try-to-disable-denoising-sha.patch @@ -0,0 +1,47 @@ +From: Ondrej Jirman +Date: Fri, 24 Jan 2020 18:25:59 +0100 +Subject: [PATCH 158/465] media: ov5640: [Experiment] Try to disable + denoising/sharpening + +Not sure how this works exactly. More tests are needed. + +Signed-off-by: Ondrej Jirman +--- + drivers/media/i2c/ov5640.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c +index 624a3c0..2f6f97e 100644 +--- a/drivers/media/i2c/ov5640.c ++++ b/drivers/media/i2c/ov5640.c +@@ -1772,6 +1772,7 @@ static int ov5640_set_mode(struct ov5640_dev *sensor) + bool auto_exp = sensor->ctrls.auto_exp->val == V4L2_EXPOSURE_AUTO; + unsigned long rate; + int ret; ++ u8 tmp; + + dn_mode = mode->dn_mode; + orig_dn_mode = orig_mode->dn_mode; +@@ -1844,6 +1845,22 @@ static int ov5640_set_mode(struct ov5640_dev *sensor) + if (ret < 0) + return ret; + ++ ret = ov5640_read_reg(sensor, 0x5308, &tmp); ++ if (ret) ++ return ret; ++ ++ ret = ov5640_write_reg(sensor, 0x5308, tmp | 0x10 | 0x40); ++ if (ret) ++ return ret; ++ ++ ret = ov5640_write_reg(sensor, 0x5306, 0); ++ if (ret) ++ return ret; ++ ++ ret = ov5640_write_reg(sensor, 0x5302, 0); ++ if (ret) ++ return ret; ++ + sensor->pending_mode_change = false; + sensor->last_mode = mode; + diff --git a/recipes-kernel/linux/linux-pinephonepro/0033-media-ov5640-Sleep-after-poweroff-to-ensure-next-pow.patch b/recipes-kernel/linux/linux-pinephonepro/0033-media-ov5640-Sleep-after-poweroff-to-ensure-next-pow.patch new file mode 100644 index 0000000..4f68862 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0033-media-ov5640-Sleep-after-poweroff-to-ensure-next-pow.patch @@ -0,0 +1,25 @@ +From: Ondrej Jirman +Date: Sun, 26 Jan 2020 00:19:40 +0100 +Subject: [PATCH 093/194] media: ov5640: Sleep after poweroff to ensure next + poweron is not too early + +It's easy to use v4l2 userspace api in such a way that user can trigger +a brownout on the sensor instead of a proper powerdown and powerup. + +Signed-off-by: Ondrej Jirman +--- + drivers/media/i2c/ov5640.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c +index 2f6f97e..8a53c0c 100644 +--- a/drivers/media/i2c/ov5640.c ++++ b/drivers/media/i2c/ov5640.c +@@ -1971,6 +1971,7 @@ static void ov5640_set_power_off(struct ov5640_dev *sensor) + ov5640_power(sensor, false); + regulator_bulk_disable(OV5640_NUM_SUPPLIES, sensor->supplies); + clk_disable_unprepare(sensor->xclk); ++ msleep(100); + } + + static int ov5640_set_power_mipi(struct ov5640_dev *sensor, bool on) diff --git a/recipes-kernel/linux/linux-pinephonepro/0034-media-ov5640-Don-t-powerup-the-sensor-during-driver-.patch b/recipes-kernel/linux/linux-pinephonepro/0034-media-ov5640-Don-t-powerup-the-sensor-during-driver-.patch new file mode 100644 index 0000000..3da1f5d --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0034-media-ov5640-Don-t-powerup-the-sensor-during-driver-.patch @@ -0,0 +1,86 @@ +From: Ondrej Jirman +Date: Sun, 26 Jan 2020 00:28:10 +0100 +Subject: [PATCH 094/194] media: ov5640: Don't powerup the sensor during + driver probe + +It causes autofocus clicking during boot on some devices, and +it's enough to do it when turning on the sensor power by media +pipeline via s_power callback, later on. + +Signed-off-by: Ondrej Jirman +--- + drivers/media/i2c/ov5640.c | 40 ++++++++-------------------------------- + 1 file changed, 8 insertions(+), 32 deletions(-) + +diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c +index 8a53c0c..75f71e6 100644 +--- a/drivers/media/i2c/ov5640.c ++++ b/drivers/media/i2c/ov5640.c +@@ -1932,6 +1932,7 @@ static void ov5640_reset(struct ov5640_dev *sensor) + static int ov5640_set_power_on(struct ov5640_dev *sensor) + { + struct i2c_client *client = sensor->i2c_client; ++ u16 chip_id; + int ret; + + ret = clk_prepare_enable(sensor->xclk); +@@ -1956,6 +1957,13 @@ static int ov5640_set_power_on(struct ov5640_dev *sensor) + if (ret) + goto power_off; + ++ ret = ov5640_read_reg16(sensor, OV5640_REG_CHIP_ID, &chip_id); ++ if (ret) { ++ dev_err(&client->dev, "%s: failed to read chip identifier\n", ++ __func__); ++ goto power_off; ++ } ++ + return 0; + + power_off: +@@ -3039,34 +3047,6 @@ static int ov5640_get_regulators(struct ov5640_dev *sensor) + sensor->supplies); + } + +-static int ov5640_check_chip_id(struct ov5640_dev *sensor) +-{ +- struct i2c_client *client = sensor->i2c_client; +- int ret = 0; +- u16 chip_id; +- +- ret = ov5640_set_power_on(sensor); +- if (ret) +- return ret; +- +- ret = ov5640_read_reg16(sensor, OV5640_REG_CHIP_ID, &chip_id); +- if (ret) { +- dev_err(&client->dev, "%s: failed to read chip identifier\n", +- __func__); +- goto power_off; +- } +- +- if (chip_id != 0x5640) { +- dev_err(&client->dev, "%s: wrong chip identifier, expected 0x5640, got 0x%x\n", +- __func__, chip_id); +- ret = -ENXIO; +- } +- +-power_off: +- ov5640_set_power_off(sensor); +- return ret; +-} +- + static int ov5640_probe(struct i2c_client *client) + { + struct device *dev = &client->dev; +@@ -3184,10 +3164,6 @@ static int ov5640_probe(struct i2c_client *client) + + mutex_init(&sensor->lock); + +- ret = ov5640_check_chip_id(sensor); +- if (ret) +- goto entity_cleanup; +- + ret = ov5640_init_controls(sensor); + if (ret) + goto entity_cleanup; diff --git a/recipes-kernel/linux/linux-pinephonepro/0035-media-ov5640-Implement-autofocus.patch b/recipes-kernel/linux/linux-pinephonepro/0035-media-ov5640-Implement-autofocus.patch new file mode 100644 index 0000000..4260878 --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0035-media-ov5640-Implement-autofocus.patch @@ -0,0 +1,405 @@ +From: Martijn Braam +Date: Mon, 28 Sep 2020 14:26:11 +0200 +Subject: [PATCH 120/194] media: ov5640: Implement autofocus + +The autofocus functionality needs a firmware blob loaded into the +internal microcontroller. + +V4L2 doesn't have an api to control all autofocus functionality, but +this at least makes it possible to focus on the center of the sensor. + +Signed-off-by: Martijn Braam +--- + drivers/media/i2c/ov5640.c | 273 +++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 273 insertions(+) + +diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c +index 75f71e6..246563e 100644 +--- a/drivers/media/i2c/ov5640.c ++++ b/drivers/media/i2c/ov5640.c +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -31,7 +32,11 @@ + + #define OV5640_DEFAULT_SLAVE_ID 0x3c + ++#define OV5640_REG_SYS_RESET00 0x3000 ++#define OV5640_REG_SYS_RESET01 0x3001 + #define OV5640_REG_SYS_RESET02 0x3002 ++#define OV5640_REG_SYS_CLOCK_ENABLE00 0x3004 ++#define OV5640_REG_SYS_CLOCK_ENABLE01 0x3005 + #define OV5640_REG_SYS_CLOCK_ENABLE02 0x3006 + #define OV5640_REG_SYS_CTRL0 0x3008 + #define OV5640_REG_SYS_CTRL0_SW_PWDN 0x42 +@@ -41,6 +46,14 @@ + #define OV5640_REG_PAD_OUTPUT_ENABLE01 0x3017 + #define OV5640_REG_PAD_OUTPUT_ENABLE02 0x3018 + #define OV5640_REG_PAD_OUTPUT00 0x3019 ++#define OV5640_REG_FW_CMD_MAIN 0x3022 ++#define OV5640_REG_FW_CMD_ACK 0x3023 ++#define OV5640_REG_FW_CMD_PARA0 0x3024 ++#define OV5640_REG_FW_CMD_PARA1 0x3025 ++#define OV5640_REG_FW_CMD_PARA2 0x3026 ++#define OV5640_REG_FW_CMD_PARA3 0x3027 ++#define OV5640_REG_FW_CMD_PARA4 0x3028 ++#define OV5640_REG_FW_STATUS 0x3029 + #define OV5640_REG_SYSTEM_CONTROL1 0x302e + #define OV5640_REG_SC_PLL_CTRL0 0x3034 + #define OV5640_REG_SC_PLL_CTRL1 0x3035 +@@ -59,6 +72,7 @@ + #define OV5640_REG_AEC_PK_MANUAL 0x3503 + #define OV5640_REG_AEC_PK_REAL_GAIN 0x350a + #define OV5640_REG_AEC_PK_VTS 0x350c ++#define OV5640_REG_VCM_CONTROL4 0x3606 + #define OV5640_REG_TIMING_DVPHO 0x3808 + #define OV5640_REG_TIMING_DVPVO 0x380a + #define OV5640_REG_TIMING_HTS 0x380c +@@ -96,6 +110,20 @@ + #define OV5640_REG_SDE_CTRL4 0x5584 + #define OV5640_REG_SDE_CTRL5 0x5585 + #define OV5640_REG_AVG_READOUT 0x56a1 ++#define OV5640_REG_FIRMWARE_BASE 0x8000 ++ ++#define OV5640_FW_STATUS_S_FIRMWARE 0x7f ++#define OV5640_FW_STATUS_S_STARTUP 0x7e ++#define OV5640_FW_STATUS_S_IDLE 0x70 ++#define OV5640_FW_STATUS_S_FOCUSING 0x00 ++#define OV5640_FW_STATUS_S_FOCUSED 0x10 ++ ++#define OV5640_FW_CMD_TRIGGER_FOCUS 0x03 ++#define OV5640_FW_CMD_CONTINUOUS_FOCUS 0x04 ++#define OV5640_FW_CMD_GET_FOCUS_RESULT 0x07 ++#define OV5640_FW_CMD_RELEASE_FOCUS 0x08 ++#define OV5640_FW_CMD_ZONE_CONFIG 0x12 ++#define OV5640_FW_CMD_DEFAULT_ZONES 0x80 + + enum ov5640_mode_id { + OV5640_MODE_QQVGA_160_120 = 0, +@@ -222,6 +250,12 @@ struct ov5640_ctrls { + struct v4l2_ctrl *auto_gain; + struct v4l2_ctrl *gain; + }; ++ struct { ++ struct v4l2_ctrl *focus_auto; ++ struct v4l2_ctrl *af_start; ++ struct v4l2_ctrl *af_stop; ++ struct v4l2_ctrl *af_status; ++ }; + struct v4l2_ctrl *brightness; + struct v4l2_ctrl *light_freq; + struct v4l2_ctrl *saturation; +@@ -265,6 +299,8 @@ struct ov5640_dev { + + bool pending_mode_change; + bool streaming; ++ ++ bool af_initialized; + }; + + static inline struct ov5640_dev *to_ov5640_dev(struct v4l2_subdev *sd) +@@ -1929,6 +1965,118 @@ static void ov5640_reset(struct ov5640_dev *sensor) + usleep_range(20000, 25000); + } + ++static int ov5640_copy_fw_to_device(struct ov5640_dev *sensor, ++ const struct firmware *fw) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ const u8 *data = (const u8 *)fw->data; ++ u8 fw_status; ++ int i; ++ int ret; ++ ++ // Putting MCU in reset state ++ ret = ov5640_write_reg(sensor, OV5640_REG_SYS_RESET00, 0x20); ++ if (ret) ++ return ret; ++ ++ // Write firmware ++ for (i = 0; i < fw->size / sizeof(u8); i++) ++ ov5640_write_reg(sensor, ++ OV5640_REG_FIRMWARE_BASE + i, ++ data[i]); ++ ++ // Reset MCU state ++ ov5640_write_reg(sensor, OV5640_REG_FW_CMD_MAIN, 0x00); ++ ov5640_write_reg(sensor, OV5640_REG_FW_CMD_ACK, 0x00); ++ ov5640_write_reg(sensor, OV5640_REG_FW_CMD_PARA0, 0x00); ++ ov5640_write_reg(sensor, OV5640_REG_FW_CMD_PARA1, 0x00); ++ ov5640_write_reg(sensor, OV5640_REG_FW_CMD_PARA2, 0x00); ++ ov5640_write_reg(sensor, OV5640_REG_FW_CMD_PARA3, 0x00); ++ ov5640_write_reg(sensor, OV5640_REG_FW_CMD_PARA4, 0x00); ++ ov5640_write_reg(sensor, OV5640_REG_FW_STATUS, 0x7f); ++ ++ // Start AF MCU ++ ret = ov5640_write_reg(sensor, OV5640_REG_SYS_RESET00, 0x00); ++ if (ret) ++ return ret; ++ ++ dev_info(&client->dev, "firmware upload success\n"); ++ ++ // Wait for firmware to be ready ++ for (i = 0; i < 5; i++) { ++ ret = ov5640_read_reg(sensor, OV5640_REG_FW_STATUS, &fw_status); ++ if (fw_status == OV5640_FW_STATUS_S_IDLE) { ++ dev_info(&client->dev, "fw started after %d ms\n", i * 50); ++ return ret; ++ } ++ msleep(50); ++ } ++ dev_err(&client->dev, "uploaded firmware didn't start, got to 0x%x, retrying...\n", fw_status); ++ ++ // Putting MCU in reset state ++ ret = ov5640_write_reg(sensor, OV5640_REG_SYS_RESET00, 0x20); ++ if (ret) ++ return ret; ++ // Start AF MCU ++ ret = ov5640_write_reg(sensor, OV5640_REG_SYS_RESET00, 0x00); ++ if (ret) ++ return ret; ++ // Wait for firmware to be ready ++ for (i = 0; i < 5; i++) { ++ ret = ov5640_read_reg(sensor, OV5640_REG_FW_STATUS, &fw_status); ++ if (fw_status == OV5640_FW_STATUS_S_IDLE) { ++ dev_info(&client->dev, "fw started after %d ms\n", i * 50); ++ return ret; ++ } ++ msleep(50); ++ } ++ dev_err(&client->dev, "uploaded firmware didn't start, got to 0x%x\n", fw_status); ++ return -ETIMEDOUT; ++} ++ ++static int ov5640_af_init(struct ov5640_dev *sensor) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ const char* fwname = "ov5640_af.bin"; ++ const struct firmware *fw; ++ int ret; ++ ++ if (sensor->af_initialized) { ++ return 0; ++ } ++ ++ if (firmware_request_nowarn(&fw, fwname, &client->dev) == 0) { ++ ret = ov5640_copy_fw_to_device(sensor, fw); ++ if (ret == 0) ++ sensor->af_initialized = 1; ++ } else { ++ dev_warn(&client->dev, "%s: no autofocus firmware available (%s)\n", ++ __func__, fwname); ++ ret = -1; ++ } ++ release_firmware(fw); ++ ++ if (ret) ++ return ret; ++ ++ // Enable AF systems ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SYS_CLOCK_ENABLE00, ++ (BIT(6) | BIT(5)), (BIT(6) | BIT(5))); ++ if (ret) ++ return ret; ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SYS_CLOCK_ENABLE01, ++ BIT(6), BIT(6)); ++ if (ret) ++ return ret; ++ ++ // Set lens focus driver on ++ ov5640_write_reg(sensor, OV5640_REG_VCM_CONTROL4, 0x3f); ++ if (ret) ++ return ret; ++ ++ return ret; ++} ++ + static int ov5640_set_power_on(struct ov5640_dev *sensor) + { + struct i2c_client *client = sensor->i2c_client; +@@ -1950,6 +2098,8 @@ static int ov5640_set_power_on(struct ov5640_dev *sensor) + goto xclk_off; + } + ++ sensor->af_initialized = 0; ++ + ov5640_reset(sensor); + ov5640_power(sensor, true); + +@@ -2467,6 +2617,35 @@ static int ov5640_set_framefmt(struct ov5640_dev *sensor, + is_jpeg ? (BIT(5) | BIT(3)) : 0); + } + ++static int ov5640_fw_command(struct ov5640_dev *sensor, int command) ++{ ++ u8 fw_ack; ++ int i; ++ int ret; ++ ++ ret = ov5640_write_reg(sensor, OV5640_REG_FW_CMD_ACK, 0x01); ++ if(ret) ++ return ret; ++ ++ ret = ov5640_write_reg(sensor, OV5640_REG_FW_CMD_MAIN, command); ++ if(ret) ++ return ret; ++ ++ for (i = 0; i < 100; i++) { ++ ret = ov5640_read_reg(sensor, OV5640_REG_FW_CMD_ACK, &fw_ack); ++ if (ret) ++ return ret; ++ ++ if (fw_ack == 0){ ++ return ret; ++ } ++ ++ msleep(50); ++ } ++ return -ETIMEDOUT; ++} ++ ++ + /* + * Sensor Controls. + */ +@@ -2583,6 +2762,41 @@ static int ov5640_set_ctrl_exposure(struct ov5640_dev *sensor, + return ret; + } + ++static int ov5640_set_ctrl_focus(struct ov5640_dev *sensor, int command) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ int ret; ++ ++ ret = ov5640_af_init(sensor); ++ if (ret) { ++ dev_err(&client->dev, "%s: no autofocus firmware loaded\n", ++ __func__); ++ return 0; ++ } ++ ++ if (command == OV5640_FW_CMD_RELEASE_FOCUS) { ++ dev_dbg(&client->dev, "%s: Releasing autofocus\n", ++ __func__); ++ return ov5640_fw_command(sensor, OV5640_FW_CMD_RELEASE_FOCUS); ++ } ++ ++ // Restart zone config ++ ret = ov5640_fw_command(sensor, OV5640_FW_CMD_ZONE_CONFIG); ++ if (ret) ++ return ret; ++ ++ // Set default focus zones ++ ret = ov5640_fw_command(sensor, OV5640_FW_CMD_DEFAULT_ZONES); ++ if (ret) ++ return ret; ++ ++ dev_dbg(&client->dev, "%s: Triggering autofocus\n", ++ __func__); ++ ++ // Start focussing ++ return ov5640_fw_command(sensor, command); ++} ++ + static int ov5640_set_ctrl_gain(struct ov5640_dev *sensor, bool auto_gain) + { + struct ov5640_ctrls *ctrls = &sensor->ctrls; +@@ -2689,6 +2903,32 @@ static int ov5640_set_ctrl_vflip(struct ov5640_dev *sensor, int value) + (BIT(2) | BIT(1)) : 0); + } + ++static int ov5640_get_af_status(struct ov5640_dev *sensor) ++{ ++ u8 fw_status; ++ int ret; ++ ++ ret = ov5640_read_reg(sensor, OV5640_REG_FW_STATUS, &fw_status); ++ if (ret) ++ return ret; ++ ++ switch (fw_status) { ++ case OV5640_FW_STATUS_S_FIRMWARE: ++ case OV5640_FW_STATUS_S_STARTUP: ++ return V4L2_AUTO_FOCUS_STATUS_FAILED; ++ break; ++ case OV5640_FW_STATUS_S_IDLE: ++ return V4L2_AUTO_FOCUS_STATUS_IDLE; ++ break; ++ case OV5640_FW_STATUS_S_FOCUSED: ++ return V4L2_AUTO_FOCUS_STATUS_REACHED; ++ break; ++ default: ++ return V4L2_AUTO_FOCUS_STATUS_BUSY; ++ break; ++ } ++} ++ + static int ov5640_g_volatile_ctrl(struct v4l2_ctrl *ctrl) + { + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); +@@ -2710,6 +2950,12 @@ static int ov5640_g_volatile_ctrl(struct v4l2_ctrl *ctrl) + return val; + sensor->ctrls.exposure->val = val; + break; ++ case V4L2_CID_FOCUS_AUTO: ++ val = ov5640_get_af_status(sensor); ++ if (val < 0) ++ return val; ++ sensor->ctrls.af_status->val = val; ++ break; + } + + return 0; +@@ -2741,6 +2987,18 @@ static int ov5640_s_ctrl(struct v4l2_ctrl *ctrl) + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = ov5640_set_ctrl_white_balance(sensor, ctrl->val); + break; ++ case V4L2_CID_FOCUS_AUTO: ++ if (ctrl->val) ++ ret = ov5640_set_ctrl_focus(sensor, OV5640_FW_CMD_CONTINUOUS_FOCUS); ++ else ++ ret = ov5640_set_ctrl_focus(sensor, OV5640_FW_CMD_RELEASE_FOCUS); ++ break; ++ case V4L2_CID_AUTO_FOCUS_START: ++ ret = ov5640_set_ctrl_focus(sensor, OV5640_FW_CMD_TRIGGER_FOCUS); ++ break; ++ case V4L2_CID_AUTO_FOCUS_STOP: ++ ret = ov5640_set_ctrl_focus(sensor, OV5640_FW_CMD_RELEASE_FOCUS); ++ break; + case V4L2_CID_HUE: + ret = ov5640_set_ctrl_hue(sensor, ctrl->val); + break; +@@ -2813,6 +3071,20 @@ static int ov5640_init_controls(struct ov5640_dev *sensor) + ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, + 0, 1023, 1, 0); + ++ /* Autofocus */ ++ ctrls->focus_auto = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_AUTO, ++ 0, 1, 1, 0); ++ ctrls->af_start = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTO_FOCUS_START, ++ 0, 1, 1, 0); ++ ctrls->af_stop = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTO_FOCUS_STOP, ++ 0, 1, 1, 0); ++ ctrls->af_status = v4l2_ctrl_new_std(hdl, ops, ++ V4L2_CID_AUTO_FOCUS_STATUS, 0, ++ (V4L2_AUTO_FOCUS_STATUS_BUSY | ++ V4L2_AUTO_FOCUS_STATUS_REACHED | ++ V4L2_AUTO_FOCUS_STATUS_FAILED), ++ 0, V4L2_AUTO_FOCUS_STATUS_IDLE); ++ + ctrls->saturation = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, + 0, 255, 1, 64); + ctrls->hue = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HUE, +@@ -2846,6 +3118,7 @@ static int ov5640_init_controls(struct ov5640_dev *sensor) + v4l2_ctrl_auto_cluster(3, &ctrls->auto_wb, 0, false); + v4l2_ctrl_auto_cluster(2, &ctrls->auto_gain, 0, true); + v4l2_ctrl_auto_cluster(2, &ctrls->auto_exp, 1, true); ++ v4l2_ctrl_cluster(4, &ctrls->focus_auto); + + sensor->sd.ctrl_handler = hdl; + return 0; diff --git a/recipes-kernel/linux/linux-pinephonepro/0036-media-ov5640-set-default-ae-target-lower.patch b/recipes-kernel/linux/linux-pinephonepro/0036-media-ov5640-set-default-ae-target-lower.patch new file mode 100644 index 0000000..009d1bb --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0036-media-ov5640-set-default-ae-target-lower.patch @@ -0,0 +1,23 @@ +From: Martijn Braam +Date: Wed, 7 Oct 2020 17:33:43 +0200 +Subject: [PATCH 121/194] media: ov5640: set default ae target lower + +The OV5640 tries to overexpose all photos by about 1 stop. This makes +the exposure target one stop lower. +--- + drivers/media/i2c/ov5640.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c +index 246563e..f01bf29 100644 +--- a/drivers/media/i2c/ov5640.c ++++ b/drivers/media/i2c/ov5640.c +@@ -3355,7 +3355,7 @@ static int ov5640_probe(struct i2c_client *client) + &ov5640_mode_data[OV5640_MODE_VGA_640_480]; + sensor->last_mode = sensor->current_mode; + +- sensor->ae_target = 52; ++ sensor->ae_target = 28; + + /* optional indication of physical rotation of sensor */ + ret = fwnode_property_read_u32(dev_fwnode(&client->dev), "rotation", diff --git a/recipes-kernel/linux/linux-pinephonepro/0037-drm-panel-hx8394-Add-driver-for-HX8394-based-HannSta.patch b/recipes-kernel/linux/linux-pinephonepro/0037-drm-panel-hx8394-Add-driver-for-HX8394-based-HannSta.patch new file mode 100644 index 0000000..fc6634b --- /dev/null +++ b/recipes-kernel/linux/linux-pinephonepro/0037-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