diff options
95 files changed, 11292 insertions, 660 deletions
diff --git a/Documentation/devicetree/bindings/power/supply/qcom/qpnp-smb2.txt b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-smb2.txt index f01eae10bf4f..30a26a8965bc 100644 --- a/Documentation/devicetree/bindings/power/supply/qcom/qpnp-smb2.txt +++ b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-smb2.txt @@ -183,6 +183,12 @@ Charger specific properties: Value type: bool Definition: Boolean flag which when present enables sw compensation for jeita +- qcom,fcc-stepping-enable + Usage: optional + Value type: bool + Definition: Boolean flag which when present enables stepwise change in FCC. + The default stepping rate is 100mA/sec. + ============================================= Second Level Nodes - SMB2 Charger Peripherals ============================================= diff --git a/arch/arm/boot/dts/qcom/dsi-panel-hx8399c-fhd-plus-video.dtsi b/arch/arm/boot/dts/qcom/dsi-panel-hx8399c-fhd-plus-video.dtsi new file mode 100644 index 000000000000..2bb0f6b5b116 --- /dev/null +++ b/arch/arm/boot/dts/qcom/dsi-panel-hx8399c-fhd-plus-video.dtsi @@ -0,0 +1,132 @@ +/* Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +&mdss_mdp { + dsi_hx8399c_truly_vid: qcom,mdss_dsi_hx8399_truly_fhd_video { + qcom,mdss-dsi-panel-name = + "hx8399c video mode dsi truly panel"; + qcom,mdss-dsi-panel-type = "dsi_video_mode"; + qcom,mdss-dsi-panel-framerate = <60>; + qcom,mdss-dsi-virtual-channel-id = <0>; + qcom,mdss-dsi-stream = <0>; + qcom,mdss-dsi-panel-width = <1080>; + qcom,mdss-dsi-panel-height = <2160>; + qcom,mdss-dsi-h-front-porch = <42>; + qcom,mdss-dsi-h-back-porch = <42>; + qcom,mdss-dsi-h-pulse-width = <10>; + qcom,mdss-dsi-h-sync-skew = <0>; + qcom,mdss-dsi-v-back-porch = <15>; + qcom,mdss-dsi-v-front-porch = <10>; + qcom,mdss-dsi-v-pulse-width = <3>; + qcom,mdss-dsi-h-left-border = <0>; + qcom,mdss-dsi-h-right-border = <0>; + qcom,mdss-dsi-v-top-border = <0>; + qcom,mdss-dsi-v-bottom-border = <0>; + qcom,mdss-pan-physical-width-dimension = <65>; + qcom,mdss-pan-physical-height-dimension = <129>; + qcom,mdss-dsi-bpp = <24>; + qcom,mdss-dsi-color-order = "rgb_swap_rgb"; + qcom,mdss-dsi-underflow-color = <0xff>; + qcom,mdss-dsi-border-color = <0>; + qcom,mdss-dsi-on-command = [ + 39 01 00 00 00 00 04 + b9 ff 83 99 + 39 01 00 00 00 00 02 + d2 88 + 39 01 00 00 00 00 0c + b1 02 04 72 92 01 + 32 aa 11 11 52 57 + 39 01 00 00 00 00 10 + b2 00 80 80 cc 05 07 5a + 11 10 10 00 1e 70 03 d4 + 39 01 00 00 00 00 2d + b4 00 ff 59 59 01 ab 00 + 00 09 00 03 05 00 28 03 + 0b 0d 21 03 02 00 0c a3 + 80 59 59 02 ab 00 00 09 + 00 03 05 00 28 03 0b 0d + 02 00 0c a3 01 + 39 01 00 00 05 00 22 + d3 00 0c 03 03 00 00 10 + 10 00 00 03 00 03 00 08 + 78 08 78 00 00 00 00 00 + 24 02 05 05 03 00 00 00 + 05 40 + 39 01 00 00 05 00 21 + d5 20 20 19 19 18 18 02 + 03 00 01 24 24 18 18 18 + 18 24 24 00 00 00 00 00 + 00 00 00 2f 2f 30 30 31 + 31 + 39 01 00 00 05 00 21 + d6 24 24 18 18 19 19 01 + 00 03 02 24 24 18 18 18 + 18 20 20 40 40 40 40 40 + 40 40 40 2f 2f 30 30 31 + 31 + 39 01 00 00 00 00 02 + bd 00 + 39 01 00 00 00 00 11 + d8 aa aa aa aa aa aa aa + aa aa ba aa aa aa ba aa + aa + 39 01 00 00 00 00 02 + bd 01 + 39 01 00 00 00 00 11 + d8 00 00 00 00 00 00 00 + 00 82 ea aa aa 82 ea aa + aa + 39 01 00 00 00 00 02 + bd 02 + 39 01 00 00 00 00 09 + d8 ff ff c0 3f ff ff c0 + 3f + 39 01 00 00 00 00 02 + bd 00 + 39 01 00 00 05 00 37 + e0 01 21 31 2d 66 6f 7b + 75 7a 81 86 89 8c 90 95 + 97 9a a1 a2 aa 9e ad b0 + 5b 57 63 7a 01 21 31 2d + 66 6f 7b 75 7a 81 86 89 + 8c 90 95 97 9a a1 a2 aa + 9e ad b0 5b 57 63 7a + 39 01 00 00 00 00 03 + b6 7e 7e + 39 01 00 00 00 00 02 + cc 08 + 05 01 00 00 96 00 02 11 00 + 05 01 00 00 32 00 02 29 00]; + qcom,mdss-dsi-off-command = [ + 05 01 00 00 32 00 02 28 00 + 05 01 00 00 96 00 02 10 00]; + qcom,mdss-dsi-on-command-state = "dsi_lp_mode"; + qcom,mdss-dsi-off-command-state = "dsi_hs_mode"; + qcom,mdss-dsi-h-sync-pulse = <0>; + qcom,mdss-dsi-traffic-mode = "non_burst_sync_event"; + qcom,mdss-dsi-lane-map = "lane_map_0123"; + qcom,mdss-dsi-bllp-eof-power-mode; + qcom,mdss-dsi-bllp-power-mode; + qcom,mdss-dsi-tx-eot-append; + qcom,mdss-dsi-lane-0-state; + qcom,mdss-dsi-lane-1-state; + qcom,mdss-dsi-lane-2-state; + qcom,mdss-dsi-lane-3-state; + qcom,mdss-dsi-t-clk-post = <0x0e>; + qcom,mdss-dsi-t-clk-pre = <0x31>; + qcom,mdss-dsi-dma-trigger = "trigger_sw"; + qcom,mdss-dsi-mdp-trigger = "none"; + qcom,mdss-dsi-lp11-init; + qcom,mdss-dsi-reset-sequence = <1 10>, <0 10>, <1 10>; + }; +}; + diff --git a/arch/arm/boot/dts/qcom/sdm660-cdp.dtsi b/arch/arm/boot/dts/qcom/sdm660-cdp.dtsi index 7db93928a369..70fc57e2594f 100644 --- a/arch/arm/boot/dts/qcom/sdm660-cdp.dtsi +++ b/arch/arm/boot/dts/qcom/sdm660-cdp.dtsi @@ -191,6 +191,13 @@ qcom,panel-supply-entries = <&dsi_panel_pwr_supply>; }; +&dsi_hx8399c_truly_vid { + qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_wled"; + qcom,mdss-dsi-bl-min-level = <1>; + qcom,mdss-dsi-bl-max-level = <4095>; + qcom,panel-supply-entries = <&dsi_panel_pwr_supply>; +}; + &mdss_dp_ctrl { pinctrl-names = "mdss_dp_active", "mdss_dp_sleep"; pinctrl-0 = <&mdss_dp_aux_active &mdss_dp_usbplug_cc_active>; diff --git a/arch/arm/boot/dts/qcom/sdm660-gpu.dtsi b/arch/arm/boot/dts/qcom/sdm660-gpu.dtsi index 5a571c2db634..953e3a20da0b 100644 --- a/arch/arm/boot/dts/qcom/sdm660-gpu.dtsi +++ b/arch/arm/boot/dts/qcom/sdm660-gpu.dtsi @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -605,6 +605,69 @@ qcom,bus-max = <0>; }; }; + + qcom,gpu-pwrlevels-6 { + #address-cells = <1>; + #size-cells = <0>; + + qcom,speed-bin = <122>; + + qcom,initial-pwrlevel = <3>; + + /* NOM */ + qcom,gpu-pwrlevel@0 { + reg = <0>; + qcom,gpu-freq = <585000000>; + qcom,bus-freq = <12>; + qcom,bus-min = <11>; + qcom,bus-max = <12>; + }; + + /* SVS_L1 */ + qcom,gpu-pwrlevel@1 { + reg = <1>; + qcom,gpu-freq = <465000000>; + qcom,bus-freq = <9>; + qcom,bus-min = <8>; + qcom,bus-max = <11>; + }; + + /* SVS */ + qcom,gpu-pwrlevel@2 { + reg = <2>; + qcom,gpu-freq = <370000000>; + qcom,bus-freq = <8>; + qcom,bus-min = <6>; + qcom,bus-max = <9>; + }; + + /* Low SVS */ + qcom,gpu-pwrlevel@3 { + reg = <3>; + qcom,gpu-freq = <266000000>; + qcom,bus-freq = <3>; + qcom,bus-min = <3>; + qcom,bus-max = <6>; + }; + + /* Min SVS */ + qcom,gpu-pwrlevel@4 { + reg = <4>; + qcom,gpu-freq = <160000000>; + qcom,bus-freq = <3>; + qcom,bus-min = <3>; + qcom,bus-max = <5>; + }; + + /* XO */ + qcom,gpu-pwrlevel@5 { + reg = <5>; + qcom,gpu-freq = <19200000>; + qcom,bus-freq = <0>; + qcom,bus-min = <0>; + qcom,bus-max = <0>; + }; + }; }; }; diff --git a/arch/arm/boot/dts/qcom/sdm660-mdss-panels.dtsi b/arch/arm/boot/dts/qcom/sdm660-mdss-panels.dtsi index 2cf4a1378778..b2f4a8ce47d3 100644 --- a/arch/arm/boot/dts/qcom/sdm660-mdss-panels.dtsi +++ b/arch/arm/boot/dts/qcom/sdm660-mdss-panels.dtsi @@ -26,6 +26,7 @@ #include "dsi-panel-truly-1080p-video.dtsi" #include "dsi-panel-rm67195-amoled-fhd-cmd.dtsi" #include "dsi-panel-lgd-incell-sw49106-fhd-video.dtsi" +#include "dsi-panel-hx8399c-fhd-plus-video.dtsi" &soc { dsi_panel_pwr_supply: dsi_panel_pwr_supply { @@ -337,3 +338,24 @@ qcom,mdss-dsi-t-clk-post = <0x0d>; qcom,mdss-dsi-t-clk-pre = <0x30>; }; + +&dsi_hx8399c_truly_vid { + qcom,mdss-dsi-panel-timings-phy-v2 = [24 1f 08 09 05 03 04 a0 + 24 1f 08 09 05 03 04 a0 + 24 1f 08 09 05 03 04 a0 + 24 1f 08 09 05 03 04 a0 + 24 1c 08 09 05 03 04 a0]; + qcom,esd-check-enabled; + qcom,mdss-dsi-panel-status-check-mode = "reg_read"; + qcom,mdss-dsi-panel-status-command = [06 01 00 01 00 00 01 0a]; + qcom,mdss-dsi-panel-status-command-state = "dsi_lp_mode"; + qcom,mdss-dsi-panel-status-value = <0x9d 0x9d 0x9d 0x9d>; + qcom,mdss-dsi-panel-on-check-value = <0x9d 0x9d 0x9d 0x9d>; + qcom,mdss-dsi-panel-status-read-length = <4>; + qcom,mdss-dsi-panel-max-error-count = <3>; + qcom,mdss-dsi-min-refresh-rate = <48>; + qcom,mdss-dsi-max-refresh-rate = <60>; + qcom,mdss-dsi-pan-enable-dynamic-fps; + qcom,mdss-dsi-pan-fps-update = + "dfps_immediate_porch_mode_vfp"; +}; diff --git a/arch/arm64/configs/msmcortex-perf_defconfig b/arch/arm64/configs/msmcortex-perf_defconfig index 031869a26722..9be3b7160ebe 100644 --- a/arch/arm64/configs/msmcortex-perf_defconfig +++ b/arch/arm64/configs/msmcortex-perf_defconfig @@ -614,6 +614,8 @@ CONFIG_EXT4_FS_SECURITY=y CONFIG_EXT4_ENCRYPTION=y CONFIG_EXT4_FS_ENCRYPTION=y CONFIG_EXT4_FS_ICE_ENCRYPTION=y +CONFIG_F2FS_FS=y +CONFIG_F2FS_FS_SECURITY=y CONFIG_QUOTA=y CONFIG_QUOTA_NETLINK_INTERFACE=y CONFIG_QFMT_V2=y diff --git a/arch/arm64/configs/msmcortex_defconfig b/arch/arm64/configs/msmcortex_defconfig index bd5435729d88..14fe16629069 100644 --- a/arch/arm64/configs/msmcortex_defconfig +++ b/arch/arm64/configs/msmcortex_defconfig @@ -637,6 +637,8 @@ CONFIG_EXT4_FS_SECURITY=y CONFIG_EXT4_ENCRYPTION=y CONFIG_EXT4_FS_ENCRYPTION=y CONFIG_EXT4_FS_ICE_ENCRYPTION=y +CONFIG_F2FS_FS=y +CONFIG_F2FS_FS_SECURITY=y CONFIG_QUOTA=y CONFIG_QUOTA_NETLINK_INTERFACE=y CONFIG_QFMT_V2=y diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c index b30bdffa3066..24b0ed38ab7d 100644 --- a/arch/arm64/mm/mmu.c +++ b/arch/arm64/mm/mmu.c @@ -656,10 +656,16 @@ static phys_addr_t pgd_pgtable_alloc(void) */ void hotplug_paging(phys_addr_t start, phys_addr_t size) { - struct page *pg; - phys_addr_t pgd_phys = pgd_pgtable_alloc(); - pgd_t *pgd = pgd_set_fixmap(pgd_phys); + phys_addr_t pgd_phys; + pgd_t *pgd; + int cpu; + + for_each_possible_cpu(cpu) + if (current->cpu != cpu) + sched_isolate_cpu(cpu); + pgd_phys = pgd_pgtable_alloc(); + pgd = pgd_set_fixmap(pgd_phys); memcpy(pgd, swapper_pg_dir, PAGE_SIZE); @@ -675,6 +681,9 @@ void hotplug_paging(phys_addr_t start, phys_addr_t size) pg = phys_to_page(pgd_phys); pgtable_page_dtor(pg); __free_pages(pg, 0); + for_each_possible_cpu(cpu) + if (current->cpu != cpu) + sched_unisolate_cpu_unlocked(cpu); } #ifdef CONFIG_MEMORY_HOTREMOVE @@ -1028,7 +1037,11 @@ void remove_pagetable(unsigned long start, unsigned long end, bool direct) unsigned long addr; pgd_t *pgd; pud_t *pud; + int cpu; + for_each_possible_cpu(cpu) + if (current->cpu != cpu) + sched_isolate_cpu(cpu); for (addr = start; addr < end; addr = next) { next = pgd_addr_end(addr, end); @@ -1049,6 +1062,9 @@ void remove_pagetable(unsigned long start, unsigned long end, bool direct) } flush_tlb_all(); + for_each_possible_cpu(cpu) + if (current->cpu != cpu) + sched_unisolate_cpu_unlocked(cpu); } diff --git a/drivers/char/adsprpc.c b/drivers/char/adsprpc.c index 18d98292a187..c0787608af56 100644 --- a/drivers/char/adsprpc.c +++ b/drivers/char/adsprpc.c @@ -2774,6 +2774,28 @@ static long fastrpc_device_ioctl(struct file *file, unsigned int ioctl_num, if (err) goto bail; break; + case FASTRPC_IOCTL_MMAP_64: + K_COPY_FROM_USER(err, 0, &p.mmap, param, + sizeof(p.mmap)); + if (err) + goto bail; + VERIFY(err, 0 == (err = fastrpc_internal_mmap(fl, &p.mmap))); + if (err) + goto bail; + K_COPY_TO_USER(err, 0, param, &p.mmap, sizeof(p.mmap)); + if (err) + goto bail; + break; + case FASTRPC_IOCTL_MUNMAP_64: + K_COPY_FROM_USER(err, 0, &p.munmap, param, + sizeof(p.munmap)); + if (err) + goto bail; + VERIFY(err, 0 == (err = fastrpc_internal_munmap(fl, + &p.munmap))); + if (err) + goto bail; + break; case FASTRPC_IOCTL_SETMODE: switch ((uint32_t)ioctl_param) { case FASTRPC_MODE_PARALLEL: diff --git a/drivers/char/adsprpc_compat.c b/drivers/char/adsprpc_compat.c index fc6450336061..e1e061748f22 100644 --- a/drivers/char/adsprpc_compat.c +++ b/drivers/char/adsprpc_compat.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2017, The Linux Foundation. All rights reserved. + * Copyright (c) 2014-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -36,6 +36,11 @@ _IOWR('R', 9, struct compat_fastrpc_ioctl_perf) #define COMPAT_FASTRPC_IOCTL_INIT_ATTRS \ _IOWR('R', 10, struct compat_fastrpc_ioctl_init_attrs) +#define COMPAT_FASTRPC_IOCTL_MMAP_64 \ + _IOWR('R', 14, struct compat_fastrpc_ioctl_mmap_64) +#define COMPAT_FASTRPC_IOCTL_MUNMAP_64 \ + _IOWR('R', 15, struct compat_fastrpc_ioctl_munmap_64) + struct compat_remote_buf { compat_uptr_t pv; /* buffer pointer */ @@ -72,11 +77,24 @@ struct compat_fastrpc_ioctl_mmap { compat_uptr_t vaddrout; /* dsps virtual address */ }; +struct compat_fastrpc_ioctl_mmap_64 { + compat_int_t fd; /* ion fd */ + compat_uint_t flags; /* flags for dsp to map with */ + compat_u64 vaddrin; /* optional virtual address */ + compat_size_t size; /* size */ + compat_u64 vaddrout; /* dsps virtual address */ +}; + struct compat_fastrpc_ioctl_munmap { compat_uptr_t vaddrout; /* address to unmap */ compat_size_t size; /* size */ }; +struct compat_fastrpc_ioctl_munmap_64 { + compat_u64 vaddrout; /* address to unmap */ + compat_size_t size; /* size */ +}; + struct compat_fastrpc_ioctl_init { compat_uint_t flags; /* one of FASTRPC_INIT_* macros */ compat_uptr_t file; /* pointer to elf file */ @@ -209,6 +227,28 @@ static int compat_get_fastrpc_ioctl_mmap( return err; } +static int compat_get_fastrpc_ioctl_mmap_64( + struct compat_fastrpc_ioctl_mmap_64 __user *map32, + struct fastrpc_ioctl_mmap __user *map) +{ + compat_uint_t u; + compat_int_t i; + compat_size_t s; + compat_u64 p; + int err; + + err = get_user(i, &map32->fd); + err |= put_user(i, &map->fd); + err |= get_user(u, &map32->flags); + err |= put_user(u, &map->flags); + err |= get_user(p, &map32->vaddrin); + err |= put_user(p, &map->vaddrin); + err |= get_user(s, &map32->size); + err |= put_user(s, &map->size); + + return err; +} + static int compat_put_fastrpc_ioctl_mmap( struct compat_fastrpc_ioctl_mmap __user *map32, struct fastrpc_ioctl_mmap __user *map) @@ -222,6 +262,19 @@ static int compat_put_fastrpc_ioctl_mmap( return err; } +static int compat_put_fastrpc_ioctl_mmap_64( + struct compat_fastrpc_ioctl_mmap_64 __user *map32, + struct fastrpc_ioctl_mmap __user *map) +{ + compat_u64 p; + int err; + + err = get_user(p, &map->vaddrout); + err |= put_user(p, &map32->vaddrout); + + return err; +} + static int compat_get_fastrpc_ioctl_munmap( struct compat_fastrpc_ioctl_munmap __user *unmap32, struct fastrpc_ioctl_munmap __user *unmap) @@ -238,6 +291,22 @@ static int compat_get_fastrpc_ioctl_munmap( return err; } +static int compat_get_fastrpc_ioctl_munmap_64( + struct compat_fastrpc_ioctl_munmap_64 __user *unmap32, + struct fastrpc_ioctl_munmap __user *unmap) +{ + compat_u64 p; + compat_size_t s; + int err; + + err = get_user(p, &unmap32->vaddrout); + err |= put_user(p, &unmap->vaddrout); + err |= get_user(s, &unmap32->size); + err |= put_user(s, &unmap->size); + + return err; +} + static int compat_get_fastrpc_ioctl_perf( struct compat_fastrpc_ioctl_perf __user *perf32, struct fastrpc_ioctl_perf __user *perf) @@ -343,6 +412,27 @@ long compat_fastrpc_device_ioctl(struct file *filp, unsigned int cmd, VERIFY(err, 0 == compat_put_fastrpc_ioctl_mmap(map32, map)); return err; } + case COMPAT_FASTRPC_IOCTL_MMAP_64: + { + struct compat_fastrpc_ioctl_mmap_64 __user *map32; + struct fastrpc_ioctl_mmap __user *map; + long ret; + + map32 = compat_ptr(arg); + VERIFY(err, NULL != (map = compat_alloc_user_space( + sizeof(*map)))); + if (err) + return -EFAULT; + VERIFY(err, 0 == compat_get_fastrpc_ioctl_mmap_64(map32, map)); + if (err) + return err; + ret = filp->f_op->unlocked_ioctl(filp, FASTRPC_IOCTL_MMAP_64, + (unsigned long)map); + if (ret) + return ret; + VERIFY(err, 0 == compat_put_fastrpc_ioctl_mmap_64(map32, map)); + return err; + } case COMPAT_FASTRPC_IOCTL_MUNMAP: { struct compat_fastrpc_ioctl_munmap __user *unmap32; @@ -360,6 +450,23 @@ long compat_fastrpc_device_ioctl(struct file *filp, unsigned int cmd, return filp->f_op->unlocked_ioctl(filp, FASTRPC_IOCTL_MUNMAP, (unsigned long)unmap); } + case COMPAT_FASTRPC_IOCTL_MUNMAP_64: + { + struct compat_fastrpc_ioctl_munmap_64 __user *unmap32; + struct fastrpc_ioctl_munmap __user *unmap; + + unmap32 = compat_ptr(arg); + VERIFY(err, NULL != (unmap = compat_alloc_user_space( + sizeof(*unmap)))); + if (err) + return -EFAULT; + VERIFY(err, 0 == compat_get_fastrpc_ioctl_munmap_64(unmap32, + unmap)); + if (err) + return err; + return filp->f_op->unlocked_ioctl(filp, FASTRPC_IOCTL_MUNMAP_64, + (unsigned long)unmap); + } case COMPAT_FASTRPC_IOCTL_INIT: /* fall through */ case COMPAT_FASTRPC_IOCTL_INIT_ATTRS: diff --git a/drivers/char/adsprpc_shared.h b/drivers/char/adsprpc_shared.h index be8d1a536d6c..a88c668440c7 100644 --- a/drivers/char/adsprpc_shared.h +++ b/drivers/char/adsprpc_shared.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -19,6 +19,8 @@ #define FASTRPC_IOCTL_INVOKE _IOWR('R', 1, struct fastrpc_ioctl_invoke) #define FASTRPC_IOCTL_MMAP _IOWR('R', 2, struct fastrpc_ioctl_mmap) #define FASTRPC_IOCTL_MUNMAP _IOWR('R', 3, struct fastrpc_ioctl_munmap) +#define FASTRPC_IOCTL_MMAP_64 _IOWR('R', 14, struct fastrpc_ioctl_mmap_64) +#define FASTRPC_IOCTL_MUNMAP_64 _IOWR('R', 15, struct fastrpc_ioctl_munmap_64) #define FASTRPC_IOCTL_INVOKE_FD _IOWR('R', 4, struct fastrpc_ioctl_invoke_fd) #define FASTRPC_IOCTL_SETMODE _IOWR('R', 5, uint32_t) #define FASTRPC_IOCTL_INIT _IOWR('R', 6, struct fastrpc_ioctl_init) @@ -171,6 +173,11 @@ struct fastrpc_ioctl_munmap { size_t size; /* size */ }; +struct fastrpc_ioctl_munmap_64 { + uint64_t vaddrout; /* address to unmap */ + size_t size; /* size */ +}; + struct fastrpc_ioctl_mmap { int fd; /* ion fd */ uint32_t flags; /* flags for dsp to map with */ @@ -179,6 +186,15 @@ struct fastrpc_ioctl_mmap { uintptr_t vaddrout; /* dsps virtual address */ }; + +struct fastrpc_ioctl_mmap_64 { + int fd; /* ion fd */ + uint32_t flags; /* flags for dsp to map with */ + uint64_t vaddrin; /* optional virtual address */ + size_t size; /* size */ + uint64_t vaddrout; /* dsps virtual address */ +}; + struct fastrpc_ioctl_perf { /* kernel performance data */ uintptr_t data; uint32_t numkeys; diff --git a/drivers/char/diag/diag_ipc_logging.h b/drivers/char/diag/diag_ipc_logging.h index b9958a433c46..4b8dd1b12c1c 100644 --- a/drivers/char/diag/diag_ipc_logging.h +++ b/drivers/char/diag/diag_ipc_logging.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2015, 2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -24,6 +24,7 @@ #define DIAG_DEBUG_MASKS 0x0010 #define DIAG_DEBUG_POWER 0x0020 #define DIAG_DEBUG_BRIDGE 0x0040 +#define DIAG_DEBUG_CONTROL 0x0080 #define DIAG_DEBUG diff --git a/drivers/char/diag/diagfwd_cntl.c b/drivers/char/diag/diagfwd_cntl.c index 10038e629e6c..d8ec09f90ec4 100644 --- a/drivers/char/diag/diagfwd_cntl.c +++ b/drivers/char/diag/diagfwd_cntl.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2011-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -47,8 +47,11 @@ static void diag_mask_update_work_fn(struct work_struct *work) void diag_cntl_channel_open(struct diagfwd_info *p_info) { - if (!p_info) + if (!p_info) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid fwd_info structure\n"); return; + } driver->mask_update |= PERIPHERAL_MASK(p_info->peripheral); queue_work(driver->cntl_wq, &driver->mask_update_work); diag_notify_md_client(p_info->peripheral, DIAG_STATUS_OPEN); @@ -58,12 +61,18 @@ void diag_cntl_channel_close(struct diagfwd_info *p_info) { uint8_t peripheral; - if (!p_info) + if (!p_info) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid fwd_info structure\n"); return; + } peripheral = p_info->peripheral; - if (peripheral >= NUM_PERIPHERALS) + if (peripheral >= NUM_PERIPHERALS) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid peripheral (%d)\n", peripheral); return; + } driver->feature[peripheral].sent_feature_mask = 0; driver->feature[peripheral].rcvd_feature_mask = 0; @@ -88,8 +97,11 @@ static void diag_stm_update_work_fn(struct work_struct *work) driver->stm_peripheral = 0; mutex_unlock(&driver->cntl_lock); - if (peripheral_mask == 0) + if (peripheral_mask == 0) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Empty Peripheral mask\n"); return; + } for (i = 0; i < NUM_PERIPHERALS; i++) { if (!driver->feature[i].stm_support) @@ -112,11 +124,18 @@ void diag_notify_md_client(uint8_t peripheral, int data) struct pid *pid_struct; struct task_struct *result; - if (peripheral > NUM_PERIPHERALS) + if (peripheral > NUM_PERIPHERALS) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid peripheral (%d)\n", peripheral); return; + } - if (driver->logging_mode != DIAG_MEMORY_DEVICE_MODE) + if (driver->logging_mode != DIAG_MEMORY_DEVICE_MODE) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid logging_mode (%d)\n", + driver->logging_mode); return; + } mutex_lock(&driver->md_session_lock); memset(&info, 0, sizeof(struct siginfo)); @@ -172,8 +191,12 @@ static void process_pd_status(uint8_t *buf, uint32_t len, uint32_t pd; int status = DIAG_STATUS_CLOSED; - if (!buf || peripheral >= NUM_PERIPHERALS || len < sizeof(*pd_msg)) + if (!buf || peripheral >= NUM_PERIPHERALS || len < sizeof(*pd_msg)) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid parameters:(!buf) = %d, peripheral = %d, len = %d, pd_msg_len = %d\n", + !buf, peripheral, len, (int)sizeof(*pd_msg)); return; + } pd_msg = (struct diag_ctrl_msg_pd_status *)buf; pd = pd_msg->pd_id; @@ -183,8 +206,11 @@ static void process_pd_status(uint8_t *buf, uint32_t len, static void enable_stm_feature(uint8_t peripheral) { - if (peripheral >= NUM_PERIPHERALS) + if (peripheral >= NUM_PERIPHERALS) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid peripheral (%d)\n", peripheral); return; + } mutex_lock(&driver->cntl_lock); driver->feature[peripheral].stm_support = ENABLE_STM; @@ -196,8 +222,11 @@ static void enable_stm_feature(uint8_t peripheral) static void enable_socket_feature(uint8_t peripheral) { - if (peripheral >= NUM_PERIPHERALS) + if (peripheral >= NUM_PERIPHERALS) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid peripheral (%d)\n", peripheral); return; + } if (driver->supports_sockets) driver->feature[peripheral].sockets_enabled = 1; @@ -207,8 +236,11 @@ static void enable_socket_feature(uint8_t peripheral) static void process_hdlc_encoding_feature(uint8_t peripheral) { - if (peripheral >= NUM_PERIPHERALS) + if (peripheral >= NUM_PERIPHERALS) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid peripheral (%d)\n", peripheral); return; + } if (driver->supports_apps_hdlc_encoding) { driver->feature[peripheral].encode_hdlc = @@ -221,8 +253,11 @@ static void process_hdlc_encoding_feature(uint8_t peripheral) static void process_upd_header_untagging_feature(uint8_t peripheral) { - if (peripheral >= NUM_PERIPHERALS) + if (peripheral >= NUM_PERIPHERALS) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid peripheral (%d)\n", peripheral); return; + } if (driver->supports_apps_header_untagging) { driver->feature[peripheral].untag_header = @@ -248,8 +283,16 @@ static void process_command_deregistration(uint8_t *buf, uint32_t len, * Perform Basic sanity. The len field is the size of the data payload. * This doesn't include the header size. */ - if (!buf || peripheral >= NUM_PERIPHERALS || len == 0) + if (!buf || peripheral >= NUM_PERIPHERALS || len == 0) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid parameters:(!buf) = %d, peripheral = %d, len = %d\n", + !buf, peripheral, len); return; + } + + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag:peripheral(%d) command deregistration packet processing started\n", + peripheral); dereg = (struct diag_ctrl_cmd_dereg *)ptr; ptr += header_len; @@ -257,8 +300,8 @@ static void process_command_deregistration(uint8_t *buf, uint32_t len, read_len += header_len - (2 * sizeof(uint32_t)); if (dereg->count_entries == 0) { - pr_debug("diag: In %s, received reg tbl with no entries\n", - __func__); + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: received reg tbl with no entries\n"); return; } @@ -277,6 +320,9 @@ static void process_command_deregistration(uint8_t *buf, uint32_t len, pr_err("diag: In %s, reading less than available, read_len: %d, len: %d count: %d\n", __func__, read_len, len, dereg->count_entries); } + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag:peripheral(%d) command deregistration packet processing complete\n", + peripheral); } static void process_command_registration(uint8_t *buf, uint32_t len, uint8_t peripheral) @@ -293,8 +339,15 @@ static void process_command_registration(uint8_t *buf, uint32_t len, * Perform Basic sanity. The len field is the size of the data payload. * This doesn't include the header size. */ - if (!buf || peripheral >= NUM_PERIPHERALS || len == 0) + if (!buf || peripheral >= NUM_PERIPHERALS || len == 0) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid parameters:(!buf) = %d, peripheral = %d, len = %d\n", + !buf, peripheral, len); return; + } + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag: peripheral(%d) command registration packet processing started\n", + peripheral); reg = (struct diag_ctrl_cmd_reg *)ptr; ptr += header_len; @@ -302,7 +355,8 @@ static void process_command_registration(uint8_t *buf, uint32_t len, read_len += header_len - (2 * sizeof(uint32_t)); if (reg->count_entries == 0) { - pr_debug("diag: In %s, received reg tbl with no entries\n", + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: In %s, received reg tbl with no entries\n", __func__); return; } @@ -322,6 +376,9 @@ static void process_command_registration(uint8_t *buf, uint32_t len, pr_err("diag: In %s, reading less than available, read_len: %d, len: %d count: %d\n", __func__, read_len, len, reg->count_entries); } + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag: peripheral(%d) command registration packet processing complete\n", + peripheral); } static void diag_close_transport_work_fn(struct work_struct *work) @@ -343,8 +400,11 @@ static void diag_close_transport_work_fn(struct work_struct *work) static void process_socket_feature(uint8_t peripheral) { - if (peripheral >= NUM_PERIPHERALS) + if (peripheral >= NUM_PERIPHERALS) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid peripheral (%d)\n", peripheral); return; + } mutex_lock(&driver->cntl_lock); driver->close_transport |= PERIPHERAL_MASK(peripheral); @@ -375,15 +435,20 @@ static void process_incoming_feature_mask(uint8_t *buf, uint32_t len, uint32_t feature_mask = 0; uint8_t *ptr = buf; - if (!buf || peripheral >= NUM_PERIPHERALS || len == 0) + if (!buf || peripheral >= NUM_PERIPHERALS || len == 0) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid parameters:(!buf) = %d, peripheral = %d, len = %d\n", + !buf, peripheral, len); return; + } header = (struct diag_ctrl_feature_mask *)ptr; ptr += header_len; feature_mask_len = header->feature_mask_len; if (feature_mask_len == 0) { - pr_debug("diag: In %s, received invalid feature mask from peripheral %d\n", + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: In %s, received invalid feature mask from peripheral %d\n", __func__, peripheral); return; } @@ -396,6 +461,8 @@ static void process_incoming_feature_mask(uint8_t *buf, uint32_t len, diag_cmd_remove_reg_by_proc(peripheral); driver->feature[peripheral].rcvd_feature_mask = 1; + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag: Received feature mask for peripheral %d\n", peripheral); for (i = 0; i < feature_mask_len && read_len < len; i++) { feature_mask = *(uint8_t *)ptr; @@ -425,6 +492,10 @@ static void process_incoming_feature_mask(uint8_t *buf, uint32_t len, process_socket_feature(peripheral); process_log_on_demand_feature(peripheral); + + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag: Peripheral(%d) feature mask is processed\n", + peripheral); } static void process_last_event_report(uint8_t *buf, uint32_t len, @@ -436,14 +507,23 @@ static void process_last_event_report(uint8_t *buf, uint32_t len, uint32_t pkt_len = sizeof(uint32_t) + sizeof(uint16_t); uint16_t event_size = 0; - if (!buf || peripheral >= NUM_PERIPHERALS || len != pkt_len) + if (!buf || peripheral >= NUM_PERIPHERALS || len != pkt_len) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid parameters:(!buf) = %d, peripheral = %d, len = %d, pkt_len = %d\n", + !buf, peripheral, len, pkt_len); return; + } + + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag:started processing last event report for peripheral (%d)\n", + peripheral); mutex_lock(&event_mask.lock); header = (struct diag_ctrl_last_event_report *)ptr; event_size = ((header->event_last_id / 8) + 1); if (event_size >= driver->event_mask_size) { - pr_debug("diag: In %s, receiving event mask size more that Apps can handle\n", + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag: In %s, receiving event mask size more that Apps can handle\n", __func__); temp = krealloc(driver->event_mask->ptr, event_size, GFP_KERNEL); @@ -461,6 +541,9 @@ static void process_last_event_report(uint8_t *buf, uint32_t len, driver->last_event_id = header->event_last_id; err: mutex_unlock(&event_mask.lock); + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag: last event report processed for peripheral (%d)\n", + peripheral); } static void process_log_range_report(uint8_t *buf, uint32_t len, @@ -474,8 +557,15 @@ static void process_log_range_report(uint8_t *buf, uint32_t len, struct diag_ctrl_log_range *log_range = NULL; struct diag_log_mask_t *mask_ptr = NULL; - if (!buf || peripheral >= NUM_PERIPHERALS || len < 0) + if (!buf || peripheral >= NUM_PERIPHERALS || len < 0) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid parameters:(!buf) = %d, peripheral = %d, len = %d\n", + !buf, peripheral, len); return; + } + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag:started processing log range report for peripheral(%d)\n", + peripheral); header = (struct diag_ctrl_log_range_report *)ptr; ptr += header_len; @@ -501,6 +591,9 @@ static void process_log_range_report(uint8_t *buf, uint32_t len, mask_ptr->range = LOG_ITEMS_TO_SIZE(log_range->num_items); mutex_unlock(&(mask_ptr->lock)); } + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag: log range report processed for peripheral (%d)\n", + peripheral); } static int update_msg_mask_tbl_entry(struct diag_msg_mask_t *mask, @@ -508,8 +601,12 @@ static int update_msg_mask_tbl_entry(struct diag_msg_mask_t *mask, { uint32_t temp_range; - if (!mask || !range) + if (!mask || !range) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid %s\n", + (!mask ? "mask" : (!range ? "range" : " "))); return -EIO; + } if (range->ssid_last < range->ssid_first) { pr_err("diag: In %s, invalid ssid range, first: %d, last: %d\n", __func__, range->ssid_first, range->ssid_last); @@ -541,8 +638,16 @@ static void process_ssid_range_report(uint8_t *buf, uint32_t len, uint8_t *temp = NULL; uint32_t min_len = header_len - sizeof(struct diag_ctrl_pkt_header_t); - if (!buf || peripheral >= NUM_PERIPHERALS || len < min_len) + if (!buf || peripheral >= NUM_PERIPHERALS || len < min_len) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid parameters:(!buf) = %d, peripheral = %d, len = %d, min_len = %d\n", + !buf, peripheral, len, min_len); return; + } + + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag: started processing ssid range for peripheral (%d)\n", + peripheral); header = (struct diag_ctrl_ssid_range_report *)ptr; ptr += header_len; @@ -594,6 +699,9 @@ static void process_ssid_range_report(uint8_t *buf, uint32_t len, driver->msg_mask_tbl_count += 1; } mutex_unlock(&driver->msg_mask_lock); + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag: processed ssid range for peripheral(%d)\n", + peripheral); } static void diag_build_time_mask_update(uint8_t *buf, @@ -610,8 +718,12 @@ static void diag_build_time_mask_update(uint8_t *buf, uint32_t *dest_ptr = NULL; struct diag_msg_mask_t *build_mask = NULL; - if (!range || !buf) + if (!range || !buf) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid %s\n", + (!range ? "range" : (!buf ? "buf" : " "))); return; + } if (range->ssid_last < range->ssid_first) { pr_err("diag: In %s, invalid ssid range, first: %d, last: %d\n", @@ -673,8 +785,16 @@ static void process_build_mask_report(uint8_t *buf, uint32_t len, struct diag_ctrl_build_mask_report *header = NULL; struct diag_ssid_range_t *range = NULL; - if (!buf || peripheral >= NUM_PERIPHERALS || len < header_len) + if (!buf || peripheral >= NUM_PERIPHERALS || len < header_len) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Invalid parameters:(!buf) = %d, peripheral = %d, len = %d, header_len = %d\n", + !buf, peripheral, len, header_len); return; + } + + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag: started processing build mask for peripheral(%d)\n", + peripheral); header = (struct diag_ctrl_build_mask_report *)ptr; ptr += header_len; @@ -690,6 +810,8 @@ static void process_build_mask_report(uint8_t *buf, uint32_t len, ptr += num_items * sizeof(uint32_t); read_len += num_items * sizeof(uint32_t); } + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag: processing build mask complete (%d)\n", peripheral); } void diag_cntl_process_read_data(struct diagfwd_info *p_info, void *buf, @@ -700,8 +822,10 @@ void diag_cntl_process_read_data(struct diagfwd_info *p_info, void *buf, uint8_t *ptr = buf; struct diag_ctrl_pkt_header_t *ctrl_pkt = NULL; - if (!buf || len <= 0 || !p_info) + if (!buf || len <= 0 || !p_info) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, "diag: Invalid parameters\n"); return; + } if (reg_dirty & PERIPHERAL_MASK(p_info->peripheral)) { pr_err_ratelimited("diag: dropping command registration from peripheral %d\n", @@ -711,6 +835,9 @@ void diag_cntl_process_read_data(struct diagfwd_info *p_info, void *buf, while (read_len + header_len < len) { ctrl_pkt = (struct diag_ctrl_pkt_header_t *)ptr; + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag:peripheral: %d: pkt_id: %d\n", + p_info->peripheral, ctrl_pkt->pkt_id); switch (ctrl_pkt->pkt_id) { case DIAG_CTRL_MSG_REG: process_command_registration(ptr, ctrl_pkt->len, @@ -745,13 +872,15 @@ void diag_cntl_process_read_data(struct diagfwd_info *p_info, void *buf, p_info->peripheral); break; default: - pr_debug("diag: Control packet %d not supported\n", - ctrl_pkt->pkt_id); + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag: Control packet %d not supported\n", + ctrl_pkt->pkt_id); } ptr += header_len + ctrl_pkt->len; read_len += header_len + ctrl_pkt->len; } - + DIAG_LOG(DIAG_DEBUG_CONTROL, + "diag: control packet processing complete\n"); return; } @@ -969,15 +1098,16 @@ void diag_real_time_work_fn(struct work_struct *work) for (i = 0; i < DIAG_NUM_PROC; i++) { temp_real_time = diag_compute_real_time(i); if (temp_real_time == driver->real_time_mode[i]) { - pr_debug("diag: did not update real time mode on proc %d, already in the req mode %d", + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: did not update real time mode on proc %d, already in the req mode %d\n", i, temp_real_time); continue; } if (i == DIAG_LOCAL_PROC) { if (!send_update) { - pr_debug("diag: In %s, cannot send real time mode pkt since one of the periperhal is in buffering mode\n", - __func__); + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: cannot send real time mode pkt since one of the periperhal is in buffering mode\n"); break; } for (j = 0; j < NUM_PERIPHERALS; j++) @@ -1011,7 +1141,8 @@ void diag_real_time_work_fn(struct work_struct *work) temp_real_time = MODE_NONREALTIME; } if (temp_real_time == driver->real_time_mode[i]) { - pr_debug("diag: did not update real time mode on proc %d, already in the req mode %d", + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: did not update real time mode on proc %d, already in the req mode %d\n", i, temp_real_time); continue; } @@ -1046,8 +1177,8 @@ static int __diag_send_real_time_update(uint8_t peripheral, int real_time, if (!driver->diagfwd_cntl[peripheral] || !driver->diagfwd_cntl[peripheral]->ch_open) { - pr_debug("diag: In %s, control channel is not open, p: %d\n", - __func__, peripheral); + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: control channel is not open, p: %d\n", peripheral); return err; } @@ -1194,8 +1325,9 @@ int diag_send_peripheral_buffering_mode(struct diag_buffering_mode_t *params) } if (!driver->feature[peripheral].peripheral_buffering) { - pr_debug("diag: In %s, peripheral %d doesn't support buffering\n", - __func__, peripheral); + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: peripheral %d doesn't support buffering\n", + peripheral); driver->buffering_flag[params->peripheral] = 0; return -EIO; } @@ -1260,8 +1392,9 @@ int diag_send_stm_state(uint8_t peripheral, uint8_t stm_control_data) if (!driver->diagfwd_cntl[peripheral] || !driver->diagfwd_cntl[peripheral]->ch_open) { - pr_debug("diag: In %s, control channel is not open, p: %d\n", - __func__, peripheral); + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: control channel is not open, p: %d\n", + peripheral); return -ENODEV; } @@ -1290,15 +1423,17 @@ int diag_send_peripheral_drain_immediate(uint8_t pd, struct diag_ctrl_drain_immediate_v2 ctrl_pkt_v2; if (!driver->feature[peripheral].peripheral_buffering) { - pr_debug("diag: In %s, peripheral %d doesn't support buffering\n", - __func__, peripheral); + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: peripheral %d doesn't support buffering\n", + peripheral); return -EINVAL; } if (!driver->diagfwd_cntl[peripheral] || !driver->diagfwd_cntl[peripheral]->ch_open) { - pr_debug("diag: In %s, control channel is not open, p: %d\n", - __func__, peripheral); + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: control channel is not open, p: %d\n", + peripheral); return -ENODEV; } @@ -1355,8 +1490,9 @@ int diag_send_buffering_tx_mode_pkt(uint8_t peripheral, } if (!driver->feature[peripheral].peripheral_buffering) { - pr_debug("diag: In %s, peripheral %d doesn't support buffering\n", - __func__, peripheral); + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: peripheral %d doesn't support buffering\n", + peripheral); return -EINVAL; } @@ -1434,15 +1570,17 @@ int diag_send_buffering_wm_values(uint8_t peripheral, } if (!driver->feature[peripheral].peripheral_buffering) { - pr_debug("diag: In %s, peripheral %d doesn't support buffering\n", - __func__, peripheral); + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: peripheral %d doesn't support buffering\n", + peripheral); return -EINVAL; } if (!driver->diagfwd_cntl[peripheral] || !driver->diagfwd_cntl[peripheral]->ch_open) { - pr_debug("diag: In %s, control channel is not open, p: %d\n", - __func__, peripheral); + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: control channel is not open, p: %d\n", + peripheral); return -ENODEV; } diff --git a/drivers/char/diag/diagfwd_peripheral.c b/drivers/char/diag/diagfwd_peripheral.c index 6b74c0056d1b..bfdce051d405 100644 --- a/drivers/char/diag/diagfwd_peripheral.c +++ b/drivers/char/diag/diagfwd_peripheral.c @@ -728,6 +728,7 @@ static void diagfwd_cntl_read_done(struct diagfwd_info *fwd_info, unsigned char *buf, int len) { if (!fwd_info) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, "diag: Invalid fwd_info\n"); diag_ws_release(); return; } @@ -748,8 +749,12 @@ static void diagfwd_cntl_read_done(struct diagfwd_info *fwd_info, */ diag_ws_on_copy_fail(DIAG_WS_MUX); /* Reset the buffer in_busy value after processing the data */ - if (fwd_info->buf_1) + if (fwd_info->buf_1) { atomic_set(&fwd_info->buf_1->in_busy, 0); + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "Buffer 1 for core PD is marked free, p: %d, t: %d\n", + fwd_info->peripheral, fwd_info->type); + } diagfwd_queue_read(fwd_info); diagfwd_queue_read(&peripheral_info[TYPE_DATA][fwd_info->peripheral]); @@ -774,8 +779,12 @@ static void diagfwd_dci_read_done(struct diagfwd_info *fwd_info, diag_dci_process_peripheral_data(fwd_info, (void *)buf, len); /* Reset the buffer in_busy value after processing the data */ - if (fwd_info->buf_1) + if (fwd_info->buf_1) { atomic_set(&fwd_info->buf_1->in_busy, 0); + DIAG_LOG(DIAG_DEBUG_DCI, + "Buffer 1 for core PD is marked free, p: %d, t: %d\n", + fwd_info->peripheral, fwd_info->type); + } diagfwd_queue_read(fwd_info); } @@ -1106,8 +1115,11 @@ void *diagfwd_request_write_buf(struct diagfwd_info *fwd_info) int index; unsigned long flags; + if (!fwd_info) + return NULL; spin_lock_irqsave(&fwd_info->write_buf_lock, flags); - for (index = 0 ; index < NUM_WRITE_BUFFERS; index++) { + for (index = 0; (index < NUM_WRITE_BUFFERS) && fwd_info->buf_ptr[index]; + index++) { if (!atomic_read(&(fwd_info->buf_ptr[index]->in_busy))) { atomic_set(&(fwd_info->buf_ptr[index]->in_busy), 1); buf = fwd_info->buf_ptr[index]->data; @@ -1529,7 +1541,8 @@ int diagfwd_write_buffer_done(struct diagfwd_info *fwd_info, const void *ptr) if (!fwd_info || !ptr) return found; spin_lock_irqsave(&fwd_info->write_buf_lock, flags); - for (index = 0; index < NUM_WRITE_BUFFERS; index++) { + for (index = 0; (index < NUM_WRITE_BUFFERS) && fwd_info->buf_ptr[index]; + index++) { if (fwd_info->buf_ptr[index]->data == ptr) { atomic_set(&fwd_info->buf_ptr[index]->in_busy, 0); found = 1; @@ -1548,13 +1561,15 @@ void diagfwd_channel_read(struct diagfwd_info *fwd_info) struct diagfwd_buf_t *temp_buf = NULL; if (!fwd_info) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, "diag: Invalid fwd_info\n"); diag_ws_release(); return; } if (!fwd_info->inited || !atomic_read(&fwd_info->opened)) { - pr_debug("diag: In %s, p: %d, t: %d, inited: %d, opened: %d ch_open: %d\n", - __func__, fwd_info->peripheral, fwd_info->type, + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: p: %d, t: %d, inited: %d, opened: %d, ch_open: %d\n", + fwd_info->peripheral, fwd_info->type, fwd_info->inited, atomic_read(&fwd_info->opened), fwd_info->ch_open); diag_ws_release(); @@ -1590,8 +1605,9 @@ void diagfwd_channel_read(struct diagfwd_info *fwd_info) atomic_set(&temp_buf->in_busy, 1); } } else { - pr_debug("diag: In %s, both buffers are empty for p: %d, t: %d\n", - __func__, fwd_info->peripheral, fwd_info->type); + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: both buffers are busy for p: %d, t: %d\n", + fwd_info->peripheral, fwd_info->type); } if (!read_buf) { diff --git a/drivers/devfreq/arm-memlat-mon.c b/drivers/devfreq/arm-memlat-mon.c index 4fb0a5ffda50..739d02300ac8 100644 --- a/drivers/devfreq/arm-memlat-mon.c +++ b/drivers/devfreq/arm-memlat-mon.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2016, The Linux Foundation. All rights reserved. + * Copyright (c) 2014-2016, 2018 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -352,6 +352,7 @@ static struct platform_driver arm_memlat_mon_driver = { .name = "arm-memlat-mon", .of_match_table = match_table, .owner = THIS_MODULE, + .suppress_bind_attrs = true, }, }; diff --git a/drivers/devfreq/bimc-bwmon.c b/drivers/devfreq/bimc-bwmon.c index 315d3a67e43e..4db5a29fa849 100644 --- a/drivers/devfreq/bimc-bwmon.c +++ b/drivers/devfreq/bimc-bwmon.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2016, The Linux Foundation. All rights reserved. + * Copyright (c) 2014-2016, 2018 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -669,6 +669,7 @@ static struct platform_driver bimc_bwmon_driver = { .name = "bimc-bwmon", .of_match_table = match_table, .owner = THIS_MODULE, + .suppress_bind_attrs = true, }, }; diff --git a/drivers/i2c/busses/i2c-msm-v2.c b/drivers/i2c/busses/i2c-msm-v2.c index 67261bc10e80..d72953f2df23 100644 --- a/drivers/i2c/busses/i2c-msm-v2.c +++ b/drivers/i2c/busses/i2c-msm-v2.c @@ -1274,10 +1274,10 @@ static int i2c_msm_dma_xfer_process(struct i2c_msm_ctrl *ctrl) tx->dir, (SPS_IOVEC_FLAG_EOT | SPS_IOVEC_FLAG_NWD)); - if (dma_desc_tx < 0) { + if (IS_ERR_OR_NULL(dma_desc_tx)) { dev_err(ctrl->dev, "error dmaengine_prep_slave_sg tx:%ld\n", PTR_ERR(dma_desc_tx)); - ret = PTR_ERR(dma_desc_tx); + ret = dma_desc_tx ? PTR_ERR(dma_desc_tx) : -ENOMEM; goto dma_xfer_end; } @@ -1292,11 +1292,11 @@ static int i2c_msm_dma_xfer_process(struct i2c_msm_ctrl *ctrl) sg_rx_itr - sg_rx, rx->dir, (SPS_IOVEC_FLAG_EOT | SPS_IOVEC_FLAG_NWD)); - if (dma_desc_rx < 0) { + if (IS_ERR_OR_NULL(dma_desc_rx)) { dev_err(ctrl->dev, "error dmaengine_prep_slave_sg rx:%ld\n", PTR_ERR(dma_desc_rx)); - ret = PTR_ERR(dma_desc_rx); + ret = dma_desc_rx ? PTR_ERR(dma_desc_rx) : -ENOMEM; goto dma_xfer_end; } diff --git a/drivers/iio/imu/inv_mpu/Kconfig b/drivers/iio/imu/inv_mpu/Kconfig new file mode 100644 index 000000000000..7505454f8763 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/Kconfig @@ -0,0 +1,63 @@ +# +# inv-mpu-iio driver for Invensense MPU devices +# + +config INV_MPU_IIO + tristate + select IIO_BUFFER + select IIO_KFIFO_BUF + select IIO_TRIGGER + select CRC32 + +choice + prompt "Chip name" + depends on INV_MPU_IIO + +config INV_MPU_IIO_ICM20648 + bool "ICM20648/ICM20948" + help + Select this if you are using a ICM20648/ICM20948 chip. + +config INV_MPU_IIO_ICM20608D + bool "ICM20608D/ICM20609/ICM20689" + help + Select this if you are using a ICM20608D/ICM20609/ICM20689 chip. + +config INV_MPU_IIO_ICM20602 + bool "ICM20602" + help + Select this if you are using a ICM20602 chip. + +config INV_MPU_IIO_ICM20690 + bool "ICM20690" + help + Select this if you are using a ICM20690 chip. + +config INV_MPU_IIO_IAM20680 + bool "IAM20680" + help + Select this if you are using a IAM20680 chip. + +endchoice + +config INV_MPU_IIO_I2C + tristate "Invensense ICM20xxx devices (I2C)" + depends on I2C && !INV_MPU6050_IIO + select INV_MPU_IIO + default n + help + This driver supports Invensense ICM20xxx devices over I2C. + This driver can be built as a module. The module will be called + inv-mpu-iio-i2c. + +config INV_MPU_IIO_SPI + tristate "Invensense ICM20xxx devices (SPI)" + depends on SPI_MASTER && !INV_MPU6050_IIO + select INV_MPU_IIO + default n + help + This driver supports Invensense ICM20xxx devices over SPI. + This driver can be built as a module. The module will be called + inv-mpu-iio-spi. + +source "drivers/iio/imu/inv_mpu/inv_test/Kconfig" diff --git a/drivers/iio/imu/inv_mpu/Makefile b/drivers/iio/imu/inv_mpu/Makefile new file mode 100644 index 000000000000..dfc4c257ef73 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/Makefile @@ -0,0 +1,61 @@ +# +# Makefile for Invensense inv-mpu-iio device. +# + +obj-$(CONFIG_INV_MPU_IIO) += inv-mpu-iio.o + +inv-mpu-iio-objs += inv_mpu_common.o +inv-mpu-iio-objs += inv_mpu_ring.o +inv-mpu-iio-objs += inv_mpu_timestamp.o +inv-mpu-iio-objs += inv_mpu_dts.o + +# chip support +ifeq ($(CONFIG_INV_MPU_IIO_ICM20648), y) +inv-mpu-iio-objs += icm20648/inv_mpu_init.o +inv-mpu-iio-objs += icm20648/inv_mpu_core.o +inv-mpu-iio-objs += icm20648/inv_mpu_parsing.o +inv-mpu-iio-objs += icm20648/inv_mpu_setup.o +inv-mpu-iio-objs += icm20648/inv_mpu_dmp_fifo.o +inv-mpu-iio-objs += icm20648/inv_slave_compass.o +inv-mpu-iio-objs += icm20648/inv_slave_pressure.o +inv-mpu-iio-objs += icm20648/inv_slave_als.o +inv-mpu-iio-objs += icm20648/inv_mpu_load_dmp.o +inv-mpu-iio-objs += icm20648/inv_mpu_selftest.o +inv-mpu-iio-objs += dmp_support/inv_mpu_misc.o +else ifeq ($(CONFIG_INV_MPU_IIO_ICM20690), y) +inv-mpu-iio-objs += icm20690/inv_mpu_init_20690.o +inv-mpu-iio-objs += icm20690/inv_mpu_core_20690.o +inv-mpu-iio-objs += icm20690/inv_mpu_parsing_20690.o +inv-mpu-iio-objs += icm20690/inv_mpu_setup_20690.o +inv-mpu-iio-objs += icm20690/inv_mpu_selftest_20690.o +inv-mpu-iio-objs += icm20690/inv_slave_compass.o +else ifeq ($(CONFIG_INV_MPU_IIO_ICM20602), y) +inv-mpu-iio-objs += icm20602/inv_mpu_init_20602.o +inv-mpu-iio-objs += icm20602/inv_mpu_core_20602.o +inv-mpu-iio-objs += icm20602/inv_mpu_parsing_20602.o +inv-mpu-iio-objs += icm20602/inv_mpu_setup_20602.o +inv-mpu-iio-objs += icm20602/inv_mpu_selftest_20602.o +else ifeq ($(CONFIG_INV_MPU_IIO_ICM20608D), y) +inv-mpu-iio-objs += icm20608d/inv_mpu_init_20608.o +inv-mpu-iio-objs += icm20608d/inv_mpu_core_20608.o +inv-mpu-iio-objs += icm20608d/inv_mpu_parsing_20608.o +inv-mpu-iio-objs += icm20608d/inv_mpu_setup_20608D.o +inv-mpu-iio-objs += icm20608d/inv_mpu_dmp_fifo.o +inv-mpu-iio-objs += icm20608d/inv_mpu_load_dmp.o +inv-mpu-iio-objs += icm20608d/inv_mpu_selftest_20608.o +inv-mpu-iio-objs += dmp_support/inv_mpu_misc.o +else ifeq ($(CONFIG_INV_MPU_IIO_IAM20680), y) +inv-mpu-iio-objs += iam20680/inv_mpu_init_20680.o +inv-mpu-iio-objs += iam20680/inv_mpu_core_20680.o +inv-mpu-iio-objs += iam20680/inv_mpu_parsing_20680.o +inv-mpu-iio-objs += iam20680/inv_mpu_setup_20680.o +inv-mpu-iio-objs += iam20680/inv_mpu_selftest_20680.o +endif + +# Bus support +obj-$(CONFIG_INV_MPU_IIO_I2C) += inv-mpu-iio-i2c.o +inv-mpu-iio-i2c-objs := inv_mpu_i2c.o +obj-$(CONFIG_INV_MPU_IIO_SPI) += inv-mpu-iio-spi.o +inv-mpu-iio-spi-objs := inv_mpu_spi.o + +obj-y += inv_test/ diff --git a/drivers/iio/imu/inv_mpu/README b/drivers/iio/imu/inv_mpu/README new file mode 100644 index 000000000000..47ff5029ee6e --- /dev/null +++ b/drivers/iio/imu/inv_mpu/README @@ -0,0 +1,117 @@ +Kernel driver inv-mpu-iio +Author: InvenSense, Inc. + + +Table of Contents +================= +- Description +- Integrating the Driver in the Linux Kernel +- Dts file +- Communicating with the Driver in Userspace + + +Description +=========== +This document describes how to install the Invensense device driver into a +Linux kernel. The supported chips are listed in Kconfig and user selects an +appropriate one from .e.g. menuconfig. + + +Integrating the Driver in the Linux Kernel +========================================== +Please add the files as follows (kernel 3.10): +- Copy mpu.h to <kernel_root>/include/linux/iio/imu/ +- Copy inv_mpu folder under <kernel_root>/drivers/iio/imu/ + +In order to see the driver in menuconfig when building the kernel, please +make modifications as shown below: + + add "source "drivers/iio/imu/inv_mpu/Kconfig"" + in <kernel_root>/drivers/iio/imu/Kconfig + + add "obj-y += inv_mpu/" + in <kernel_root>/drivers/iio/imu/Makefile + + + +Dts file +======== +In order to recognize the Invensense device on the I2C/SPI bus, dts(or dtsi) +file must be modified. + +Example) +ICM20648 + AK09911/BMP280/APDS9930 on AUX I2C + + i2c@f9968000 { + /* Invensense */ + mpu6515_acc@68 { + compatible = "inven,icm20648"; + reg = <0x68>; + interrupt-parent = <&msmgpio>; + interrupts = <73 0x2>; + inven,vdd_ana-supply = <&pm8941_l17>; + inven,vcc_i2c-supply = <&pm8941_lvs1>; + inven,gpio_int1 = <&msmgpio 73 0x00>; + fs_range = <0x00>; + /* mount matrix */ + axis_map_x = <1>; + axis_map_y = <0>; + axis_map_z = <2>; + negate_x = <0>; + negate_y = <0>; + negate_z = <1>; + poll_interval = <200>; + min_interval = <5>; + inven,secondary_reg = <0x0c>; + /* If no compass sensor, + * replace "compass" with "none" + */ + inven,secondary_type = "compass"; + inven,secondary_name = "ak09911"; + inven,secondary_axis_map_x = <1>; + inven,secondary_axis_map_y = <0>; + inven,secondary_axis_map_z = <2>; + inven,secondary_negate_x = <1>; + inven,secondary_negate_y = <1>; + inven,secondary_negate_z = <1>; + /* If no pressure sensor, + * replace "pressure" with "none" + */ + inven,aux_type = "pressure"; + inven,aux_name = "bmp280"; + inven,aux_reg = <0x76>; + /* If no ALS sensor + * replace "als" with "none" + */ + inven,read_only_slave_type = "als"; + inven,read_only_slave_name = "apds9930"; + inven,read_only_slave_reg = <0x39>; + }; + }; + + +Communicating with the Driver in Userspace +========================================== +The driver generates several files in sysfs upon installation. +These files are used to communicate with the driver. The files can be found at: + +(I2C) /sys/devices/*.i2c/i2c-*/*-*/iio:device* +(SPI) /sys/devices/*.spi/spi_master/spi*/spi*.*/iio:device* + +Group and Owner for all entries should be updated to system/system at +boot time to allow userspace to access properly. + + +License +======= +Copyright (C) 2018 InvenSense, Inc. + +This software is licensed under the terms of the GNU General Public +License version 2, as published by the Free Software Foundation, and +may be copied, distributed, and modified under those terms. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + diff --git a/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_core_20680.c b/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_core_20680.c new file mode 100644 index 000000000000..b429f57be5ca --- /dev/null +++ b/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_core_20680.c @@ -0,0 +1,1072 @@ +/* + * Copyright (C) 2017-2018 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#define pr_fmt(fmt) "inv_mpu: " fmt + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/sysfs.h> +#include <linux/jiffies.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/poll.h> +#include <linux/miscdevice.h> +#include <linux/spinlock.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> + +#include "../inv_mpu_iio.h" + +static const struct inv_hw_s hw_info[INV_NUM_PARTS] = { + {128, "ICM20608D"}, + {128, "ICM20690"}, + {128, "ICM20602"}, + {128, "IAM20680"}, +}; + +#ifndef SUPPORT_ONLY_BASIC_FEATURES +static char debug_reg_addr = 0x6; +#endif + +const char sensor_l_info[][30] = { + "SENSOR_L_ACCEL", + "SENSOR_L_GYRO", + "SENSOR_L_MAG", + "SENSOR_L_ALS", + "SENSOR_L_SIXQ", + "SENSOR_L_THREEQ", + "SENSOR_L_NINEQ", + "SENSOR_L_PEDQ", + "SENSOR_L_GEOMAG", + "SENSOR_L_PRESSURE", + "SENSOR_L_GYRO_CAL", + "SENSOR_L_MAG_CAL", + "SENSOR_L_EIS_GYRO", + "SENSOR_L_ACCEL_WAKE", + "SENSOR_L_GYRO_WAKE", + "SENSOR_L_MAG_WAKE", + "SENSOR_L_ALS_WAKE", + "SENSOR_L_SIXQ_WAKE", + "SENSOR_L_NINEQ_WAKE", + "SENSOR_L_PEDQ_WAKE", + "SENSOR_L_GEOMAG_WAKE", + "SENSOR_L_PRESSURE_WAKE", + "SENSOR_L_GYRO_CAL_WAKE", + "SENSOR_L_MAG_CAL_WAKE", + "SENSOR_L_NUM_MAX", +}; + +static int inv_set_accel_bias_reg(struct inv_mpu_state *st, + int accel_bias, int axis) +{ + int accel_reg_bias; + u8 addr; + u8 d[2]; + int result = 0; + + switch (axis) { + case 0: + /* X */ + addr = REG_XA_OFFS_H; + break; + case 1: + /* Y */ + addr = REG_YA_OFFS_H; + break; + case 2: + /* Z* */ + addr = REG_ZA_OFFS_H; + break; + default: + result = -EINVAL; + goto accel_bias_set_err; + } + + result = inv_plat_read(st, addr, 2, d); + if (result) + goto accel_bias_set_err; + accel_reg_bias = ((int)d[0] << 8) | d[1]; + + /* accel_bias is 2g scaled by 1<<16. + * Convert to 16g, and mask bit0 */ + accel_reg_bias -= ((accel_bias / 8 / 65536) & ~1); + + d[0] = (accel_reg_bias >> 8) & 0xff; + d[1] = (accel_reg_bias) & 0xff; + result = inv_plat_single_write(st, addr, d[0]); + if (result) + goto accel_bias_set_err; + result = inv_plat_single_write(st, addr + 1, d[1]); + if (result) + goto accel_bias_set_err; + +accel_bias_set_err: + return result; +} + +static int inv_set_gyro_bias_reg(struct inv_mpu_state *st, + const int gyro_bias, int axis) +{ + int gyro_reg_bias; + u8 addr; + u8 d[2]; + int result = 0; + + switch (axis) { + case 0: + /* X */ + addr = REG_XG_OFFS_USR_H; + break; + case 1: + /* Y */ + addr = REG_YG_OFFS_USR_H; + break; + case 2: + /* Z */ + addr = REG_ZG_OFFS_USR_H; + break; + default: + result = -EINVAL; + goto gyro_bias_set_err; + } + + /* gyro_bias is 2000dps scaled by 1<<16. + * Convert to 1000dps */ + gyro_reg_bias = (-gyro_bias * 2 / 65536); + + d[0] = (gyro_reg_bias >> 8) & 0xff; + d[1] = (gyro_reg_bias) & 0xff; + result = inv_plat_single_write(st, addr, d[0]); + if (result) + goto gyro_bias_set_err; + result = inv_plat_single_write(st, addr + 1, d[1]); + if (result) + goto gyro_bias_set_err; + +gyro_bias_set_err: + return result; +} + +static int _bias_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int result, data; + + result = inv_switch_power_in_lp(st, true); + if (result) + return result; + + result = kstrtoint(buf, 10, &data); + if (result) + goto bias_store_fail; + switch (this_attr->address) { + case ATTR_ACCEL_X_OFFSET: + result = inv_set_accel_bias_reg(st, data, 0); + if (result) + goto bias_store_fail; + st->input_accel_bias[0] = data; + break; + case ATTR_ACCEL_Y_OFFSET: + result = inv_set_accel_bias_reg(st, data, 1); + if (result) + goto bias_store_fail; + st->input_accel_bias[1] = data; + break; + case ATTR_ACCEL_Z_OFFSET: + result = inv_set_accel_bias_reg(st, data, 2); + if (result) + goto bias_store_fail; + st->input_accel_bias[2] = data; + break; + case ATTR_GYRO_X_OFFSET: + result = inv_set_gyro_bias_reg(st, data, 0); + if (result) + goto bias_store_fail; + st->input_gyro_bias[0] = data; + break; + case ATTR_GYRO_Y_OFFSET: + result = inv_set_gyro_bias_reg(st, data, 1); + if (result) + goto bias_store_fail; + st->input_gyro_bias[1] = data; + break; + case ATTR_GYRO_Z_OFFSET: + result = inv_set_gyro_bias_reg(st, data, 2); + if (result) + goto bias_store_fail; + st->input_gyro_bias[2] = data; + break; + default: + break; + } + +bias_store_fail: + if (result) + return result; + result = inv_switch_power_in_lp(st, false); + if (result) + return result; + + return count; +} + +static ssize_t inv_bias_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + int result; + + mutex_lock(&indio_dev->mlock); + result = _bias_store(dev, attr, buf, count); + mutex_unlock(&indio_dev->mlock); + + return result; +} + +#ifndef SUPPORT_ONLY_BASIC_FEATURES +static ssize_t inv_debug_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int result, data; + + result = kstrtoint(buf, 10, &data); + if (result) + return result; + switch (this_attr->address) { + case ATTR_DMP_LP_EN_OFF: + st->chip_config.lp_en_mode_off = !!data; + inv_switch_power_in_lp(st, !!data); + break; + case ATTR_DMP_CLK_SEL: + st->chip_config.clk_sel = !!data; + inv_switch_power_in_lp(st, !!data); + break; + case ATTR_DEBUG_REG_ADDR: + debug_reg_addr = data; + break; + case ATTR_DEBUG_REG_WRITE: + inv_plat_single_write(st, debug_reg_addr, data); + break; + } + return count; +} +#endif + +static int _misc_attr_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int result, data; + + result = inv_switch_power_in_lp(st, true); + if (result) + return result; + result = kstrtoint(buf, 10, &data); + if (result) + return result; + switch (this_attr->address) { + case ATTR_GYRO_SCALE: + if (data > 3) + return -EINVAL; + st->chip_config.fsr = data; + result = inv_set_gyro_sf(st); + return result; + case ATTR_ACCEL_SCALE: + if (data > 3) + return -EINVAL; + st->chip_config.accel_fs = data; + result = inv_set_accel_sf(st); + return result; + default: + return -EINVAL; + } + st->trigger_state = MISC_TRIGGER; + result = set_inv_enable(indio_dev); + + return result; +} + +/* + * inv_misc_attr_store() - calling this function + */ +static ssize_t inv_misc_attr_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + int result; + + mutex_lock(&indio_dev->mlock); + result = _misc_attr_store(dev, attr, buf, count); + mutex_unlock(&indio_dev->mlock); + if (result) + return result; + + return count; +} + +static ssize_t inv_sensor_rate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + return snprintf(buf, MAX_WR_SZ, "%d\n", + st->sensor_l[this_attr->address].rate); +} + +static ssize_t inv_sensor_rate_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int data, rate, ind; + int result; + + result = kstrtoint(buf, 10, &data); + if (result) + return -EINVAL; + if (data <= 0) { + pr_err("sensor_rate_store: invalid data=%d\n", data); + return -EINVAL; + } + ind = this_attr->address; + rate = inv_rate_convert(st, ind, data); + + pr_debug("sensor [%s] requested rate %d input [%d]\n", + sensor_l_info[ind], rate, data); + + if (rate == st->sensor_l[ind].rate) + return count; + mutex_lock(&indio_dev->mlock); + st->sensor_l[ind].rate = rate; + st->trigger_state = DATA_TRIGGER; + inv_check_sensor_on(st); + result = set_inv_enable(indio_dev); + pr_debug("%s rate %d div %d\n", sensor_l_info[ind], + st->sensor_l[ind].rate, st->sensor_l[ind].div); + mutex_unlock(&indio_dev->mlock); + + return count; +} + +static ssize_t inv_sensor_on_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + return snprintf(buf, MAX_WR_SZ, "%d\n", st->sensor_l[this_attr->address].on); +} + +static ssize_t inv_sensor_on_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int data, on, ind; + int result; + + result = kstrtoint(buf, 10, &data); + if (result) + return -EINVAL; + if (data < 0) { + pr_err("sensor_on_store: invalid data=%d\n", data); + return -EINVAL; + } + ind = this_attr->address; + on = !!data; + + pr_debug("sensor [%s] requested %s, input [%d]\n", + sensor_l_info[ind], (on == 1) ? "On" : "Off", data); + + if (on == st->sensor_l[ind].on) { + pr_debug("sensor [%s] is already %s, input [%d]\n", + sensor_l_info[ind], (on == 1) ? "On" : "Off", data); + return count; + } + + mutex_lock(&indio_dev->mlock); + st->sensor_l[ind].on = on; + st->trigger_state = RATE_TRIGGER; + inv_check_sensor_on(st); + result = set_inv_enable(indio_dev); + mutex_unlock(&indio_dev->mlock); + if (result) + return result; + + pr_debug("Sensor [%s] is %s by sysfs\n", + sensor_l_info[ind], (on == 1) ? "On" : "Off"); + return count; +} + +static int inv_check_l_step(struct inv_mpu_state *st) +{ + if (st->step_counter_l_on || st->step_counter_wake_l_on) + st->ped.on = true; + else + st->ped.on = false; + + return 0; +} + +static int _basic_attr_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int data; + int result; + u32 power_on_data; + + result = kstrtoint(buf, 10, &data); + if (result || (data < 0)) + return -EINVAL; + + switch (this_attr->address) { + case ATTR_DMP_PED_ON: + if ((!!data) == st->ped.on) + return count; + st->ped.on = !!data; + break; + case ATTR_DMP_TILT_ENABLE: + if ((!!data) == st->chip_config.tilt_enable) + return count; + st->chip_config.tilt_enable = !!data; + pr_info("Tile %s\n", + st->chip_config.tilt_enable == + 1 ? "Enabled" : "Disabled"); + break; + case ATTR_DMP_PICK_UP_ENABLE: + if ((!!data) == st->chip_config.pick_up_enable) { + pr_info("Pick_up enable already %s\n", + st->chip_config.pick_up_enable == + 1 ? "Enabled" : "Disabled"); + return count; + } + st->chip_config.pick_up_enable = !!data; + pr_info("Pick up %s\n", + st->chip_config.pick_up_enable == + 1 ? "Enable" : "Disable"); + break; + case ATTR_IN_POWER_ON: + { + u8 p0[2]; + u8 p1[2]; + + power_on_data = (u32)data; + p0[0] = (power_on_data & 0xff); + p0[1] = ((power_on_data >> 8) & 0xff); + p1[0] = ((power_on_data >> 16) & 0xff); + p1[1] = ((power_on_data >> 24) & 0xff); + + if (st->bus_type == BUS_SPI) { + struct spi_transfer power_on; + struct spi_message msg; + + memset(&power_on, 0, sizeof(struct spi_transfer)); + + power_on.bits_per_word = 8; + power_on.len = 2; + + power_on.tx_buf = p0; + power_on.rx_buf = p1; + spi_message_init(&msg); + spi_message_add_tail(&power_on, &msg); + spi_sync(to_spi_device(st->dev), &msg); + + } else if (st->bus_type == BUS_I2C) { + struct i2c_msg msgs[2]; + + p0[0] &= 0x7f; + + msgs[0].addr = st->i2c_addr; + msgs[0].flags = 0; /* write */ + msgs[0].buf = &p0[0]; + msgs[0].len = 1; + + msgs[1].addr = st->i2c_addr; + msgs[1].flags = I2C_M_RD; + msgs[1].buf = &p1[1]; + msgs[1].len = 1; + + result = i2c_transfer(st->sl_handle, msgs, 2); + if (result < 2) + return -EIO; + } + st->power_on_data = ((p0[0] << 24) | (p0[1] << 16) | + (p1[0] << 8) | p1[1]); + return count; + } + case ATTR_DMP_EIS_ENABLE: + if ((!!data) == st->chip_config.eis_enable) + return count; + st->chip_config.eis_enable = !!data; + pr_info("Eis %s\n", + st->chip_config.eis_enable == 1 ? "Enable" : "Disable"); + break; + case ATTR_DMP_STEP_DETECTOR_ON: + st->step_detector_l_on = !!data; + break; + case ATTR_DMP_STEP_DETECTOR_WAKE_ON: + st->step_detector_wake_l_on = !!data; + break; + case ATTR_DMP_STEP_COUNTER_ON: + st->step_counter_l_on = !!data; + break; + case ATTR_DMP_STEP_COUNTER_WAKE_ON: + st->step_counter_wake_l_on = !!data; + break; + case ATTR_DMP_BATCHMODE_TIMEOUT: + if (data == st->batch.timeout) + return count; + st->batch.timeout = data; + break; + default: + return -EINVAL; + }; + inv_check_l_step(st); + inv_check_sensor_on(st); + + st->trigger_state = EVENT_TRIGGER; + result = set_inv_enable(indio_dev); + if (result) + return result; + + return count; +} + +/* + * inv_basic_attr_store() + */ +static ssize_t inv_basic_attr_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + int result; + + mutex_lock(&indio_dev->mlock); + result = _basic_attr_store(dev, attr, buf, count); + + mutex_unlock(&indio_dev->mlock); + + return result; +} + +/* + * inv_attr_show() + */ +static ssize_t inv_attr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + s8 *m; + + switch (this_attr->address) { + case ATTR_GYRO_SCALE: + { + const s16 gyro_scale[] = { 250, 500, 1000, 2000 }; + + return snprintf(buf, MAX_WR_SZ, "%d\n", + gyro_scale[st->chip_config.fsr]); + } + case ATTR_ACCEL_SCALE: + { + const s16 accel_scale[] = { 2, 4, 8, 16 }; + return snprintf(buf, MAX_WR_SZ, "%d\n", + accel_scale[st->chip_config.accel_fs]); + } + case ATTR_GYRO_ENABLE: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->chip_config.gyro_enable); + case ATTR_ACCEL_ENABLE: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->chip_config.accel_enable); + case ATTR_IN_POWER_ON: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->power_on_data); + case ATTR_DMP_BATCHMODE_TIMEOUT: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->batch.timeout); + case ATTR_DMP_PED_ON: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->ped.on); + case ATTR_DMP_TILT_ENABLE: + return snprintf(buf, MAX_WR_SZ, "%d\n", + st->chip_config.tilt_enable); + case ATTR_DMP_PICK_UP_ENABLE: + return snprintf(buf, MAX_WR_SZ, "%d\n", + st->chip_config.pick_up_enable); + case ATTR_DMP_EIS_ENABLE: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->chip_config.eis_enable); + case ATTR_DMP_LP_EN_OFF: + return snprintf(buf, MAX_WR_SZ, "%d\n", + st->chip_config.lp_en_mode_off); + case ATTR_DMP_STEP_COUNTER_ON: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->step_counter_l_on); + case ATTR_DMP_STEP_COUNTER_WAKE_ON: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->step_counter_wake_l_on); + case ATTR_DMP_STEP_DETECTOR_ON: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->step_detector_l_on); + case ATTR_DMP_STEP_DETECTOR_WAKE_ON: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->step_detector_wake_l_on); + case ATTR_GYRO_MATRIX: + m = st->plat_data.orientation; + return snprintf(buf, MAX_WR_SZ, "%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], + m[8]); + case ATTR_ACCEL_MATRIX: + m = st->plat_data.orientation; + return snprintf(buf, MAX_WR_SZ, "%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], + m[8]); + case ATTR_GYRO_SF: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->gyro_sf); + case ATTR_ANGLVEL_X_ST_CALIBBIAS: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->gyro_st_bias[0]); + case ATTR_ANGLVEL_Y_ST_CALIBBIAS: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->gyro_st_bias[1]); + case ATTR_ANGLVEL_Z_ST_CALIBBIAS: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->gyro_st_bias[2]); + case ATTR_ACCEL_X_ST_CALIBBIAS: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->accel_st_bias[0]); + case ATTR_ACCEL_Y_ST_CALIBBIAS: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->accel_st_bias[1]); + case ATTR_ACCEL_Z_ST_CALIBBIAS: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->accel_st_bias[2]); + case ATTR_GYRO_X_OFFSET: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->input_gyro_bias[0]); + case ATTR_GYRO_Y_OFFSET: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->input_gyro_bias[1]); + case ATTR_GYRO_Z_OFFSET: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->input_gyro_bias[2]); + case ATTR_ACCEL_X_OFFSET: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->input_accel_bias[0]); + case ATTR_ACCEL_Y_OFFSET: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->input_accel_bias[1]); + case ATTR_ACCEL_Z_OFFSET: + return snprintf(buf, MAX_WR_SZ, "%d\n", st->input_accel_bias[2]); + default: + return -EPERM; + } +} + +static ssize_t inv_self_test(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + int res; + + mutex_lock(&indio_dev->mlock); + res = inv_hw_self_test(st); + set_inv_enable(indio_dev); + mutex_unlock(&indio_dev->mlock); + + return snprintf(buf, MAX_WR_SZ, "%d\n", res); +} + + +/* + * inv_temperature_show() - Read temperature data directly from registers. + */ +static ssize_t inv_temperature_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + + u8 data[2]; + s32 temp; + int res; + + mutex_lock(&indio_dev->mlock); + res = inv_plat_read(st, REG_RAW_TEMP, 2, data); + if (res) + return res; + mutex_unlock(&indio_dev->mlock); + + temp = (s32)be16_to_cpup((__be16 *)(data)) * 10000; + temp = temp / TEMP_SENSITIVITY + TEMP_OFFSET; + + return snprintf(buf, MAX_WR_SZ, "%d %lld\n", temp, get_time_ns()); +} + +/* + * inv_reg_dump_show() - Register dump for testing. + */ +static ssize_t inv_reg_dump_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ii; + char data; + int bytes_printed = 0; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + + mutex_lock(&indio_dev->mlock); + bytes_printed += snprintf(buf + bytes_printed, MAX_WR_SZ, "bank 0\n"); + + for (ii = 0; ii < 0x7F; ii++) { + /* don't read fifo r/w register */ + if ((ii == REG_MEM_R_W) || (ii == REG_FIFO_R_W)) + data = 0; + else + inv_plat_read(st, ii, 1, &data); + bytes_printed += snprintf(buf + bytes_printed, MAX_WR_SZ, + "%#2x: %#2x\n", ii, data); + } + set_inv_enable(indio_dev); + mutex_unlock(&indio_dev->mlock); + + return bytes_printed; +} + +static ssize_t inv_flush_batch_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + int result, data; + + result = kstrtoint(buf, 10, &data); + if (result) + return result; + + mutex_lock(&indio_dev->mlock); + result = inv_flush_batch_data(indio_dev, data); + mutex_unlock(&indio_dev->mlock); + + return count; +} + +static const struct iio_chan_spec inv_mpu_channels[] = { + IIO_CHAN_SOFT_TIMESTAMP(INV_MPU_SCAN_TIMESTAMP), +}; + +/* special run time sysfs entry, read only */ +static DEVICE_ATTR(debug_reg_dump, S_IRUGO | S_IWUSR, inv_reg_dump_show, NULL); +static DEVICE_ATTR(out_temperature, S_IRUGO | S_IWUSR, + inv_temperature_show, NULL); +static DEVICE_ATTR(misc_self_test, S_IRUGO | S_IWUSR, inv_self_test, NULL); + +static IIO_DEVICE_ATTR(info_anglvel_matrix, S_IRUGO, inv_attr_show, NULL, + ATTR_GYRO_MATRIX); +static IIO_DEVICE_ATTR(info_accel_matrix, S_IRUGO, inv_attr_show, NULL, + ATTR_ACCEL_MATRIX); + +static IIO_DEVICE_ATTR(info_gyro_sf, S_IRUGO, inv_attr_show, NULL, + ATTR_GYRO_SF); +/* write only sysfs */ +static DEVICE_ATTR(misc_flush_batch, S_IWUSR, NULL, inv_flush_batch_store); + +/* sensor on/off sysfs control */ +static IIO_DEVICE_ATTR(in_accel_enable, S_IRUGO | S_IWUSR, + inv_sensor_on_show, inv_sensor_on_store, SENSOR_L_ACCEL); +static IIO_DEVICE_ATTR(in_anglvel_enable, S_IRUGO | S_IWUSR, + inv_sensor_on_show, inv_sensor_on_store, SENSOR_L_GYRO); +#ifndef SUPPORT_ONLY_BASIC_FEATURES +static IIO_DEVICE_ATTR(in_eis_enable, S_IRUGO | S_IWUSR, + inv_sensor_on_show, inv_sensor_on_store, + SENSOR_L_EIS_GYRO); +#endif +static IIO_DEVICE_ATTR(in_accel_wake_enable, S_IRUGO | S_IWUSR, + inv_sensor_on_show, inv_sensor_on_store, + SENSOR_L_ACCEL_WAKE); +static IIO_DEVICE_ATTR(in_anglvel_wake_enable, S_IRUGO | S_IWUSR, + inv_sensor_on_show, inv_sensor_on_store, + SENSOR_L_GYRO_WAKE); + +/* sensor rate sysfs control */ +static IIO_DEVICE_ATTR(in_accel_rate, S_IRUGO | S_IWUSR, + inv_sensor_rate_show, inv_sensor_rate_store, + SENSOR_L_ACCEL); +static IIO_DEVICE_ATTR(in_anglvel_rate, S_IRUGO | S_IWUSR, inv_sensor_rate_show, + inv_sensor_rate_store, SENSOR_L_GYRO); +#ifndef SUPPORT_ONLY_BASIC_FEATURES +static IIO_DEVICE_ATTR(in_eis_rate, S_IRUGO | S_IWUSR, + inv_sensor_rate_show, inv_sensor_rate_store, + SENSOR_L_EIS_GYRO); +#endif +static IIO_DEVICE_ATTR(in_accel_wake_rate, S_IRUGO | S_IWUSR, + inv_sensor_rate_show, inv_sensor_rate_store, + SENSOR_L_ACCEL_WAKE); +static IIO_DEVICE_ATTR(in_anglvel_wake_rate, S_IRUGO | S_IWUSR, + inv_sensor_rate_show, inv_sensor_rate_store, + SENSOR_L_GYRO_WAKE); + +static IIO_DEVICE_ATTR(misc_batchmode_timeout, S_IRUGO | S_IWUSR, + inv_attr_show, inv_basic_attr_store, + ATTR_DMP_BATCHMODE_TIMEOUT); + +/* engine scale */ +static IIO_DEVICE_ATTR(in_accel_scale, S_IRUGO | S_IWUSR, inv_attr_show, + inv_misc_attr_store, ATTR_ACCEL_SCALE); +static IIO_DEVICE_ATTR(in_anglvel_scale, S_IRUGO | S_IWUSR, inv_attr_show, + inv_misc_attr_store, ATTR_GYRO_SCALE); + + +#ifndef SUPPORT_ONLY_BASIC_FEATURES +static IIO_DEVICE_ATTR(debug_lp_en_off, S_IRUGO | S_IWUSR, inv_attr_show, + inv_debug_store, ATTR_DMP_LP_EN_OFF); +static IIO_DEVICE_ATTR(debug_clock_sel, S_IRUGO | S_IWUSR, inv_attr_show, + inv_debug_store, ATTR_DMP_CLK_SEL); +static IIO_DEVICE_ATTR(debug_reg_write, S_IRUGO | S_IWUSR, inv_attr_show, + inv_debug_store, ATTR_DEBUG_REG_WRITE); +static IIO_DEVICE_ATTR(debug_reg_write_addr, S_IRUGO | S_IWUSR, inv_attr_show, + inv_debug_store, ATTR_DEBUG_REG_ADDR); +#endif + +static IIO_DEVICE_ATTR(in_accel_x_st_calibbias, S_IRUGO | S_IWUSR, + inv_attr_show, NULL, ATTR_ACCEL_X_ST_CALIBBIAS); +static IIO_DEVICE_ATTR(in_accel_y_st_calibbias, S_IRUGO | S_IWUSR, + inv_attr_show, NULL, ATTR_ACCEL_Y_ST_CALIBBIAS); +static IIO_DEVICE_ATTR(in_accel_z_st_calibbias, S_IRUGO | S_IWUSR, + inv_attr_show, NULL, ATTR_ACCEL_Z_ST_CALIBBIAS); + +static IIO_DEVICE_ATTR(in_anglvel_x_st_calibbias, S_IRUGO | S_IWUSR, + inv_attr_show, NULL, ATTR_ANGLVEL_X_ST_CALIBBIAS); +static IIO_DEVICE_ATTR(in_anglvel_y_st_calibbias, S_IRUGO | S_IWUSR, + inv_attr_show, NULL, ATTR_ANGLVEL_Y_ST_CALIBBIAS); +static IIO_DEVICE_ATTR(in_anglvel_z_st_calibbias, S_IRUGO | S_IWUSR, + inv_attr_show, NULL, ATTR_ANGLVEL_Z_ST_CALIBBIAS); + +static IIO_DEVICE_ATTR(in_accel_x_offset, S_IRUGO | S_IWUSR, + inv_attr_show, inv_bias_store, ATTR_ACCEL_X_OFFSET); +static IIO_DEVICE_ATTR(in_accel_y_offset, S_IRUGO | S_IWUSR, + inv_attr_show, inv_bias_store, ATTR_ACCEL_Y_OFFSET); +static IIO_DEVICE_ATTR(in_accel_z_offset, S_IRUGO | S_IWUSR, + inv_attr_show, inv_bias_store, ATTR_ACCEL_Z_OFFSET); + +static IIO_DEVICE_ATTR(in_anglvel_x_offset, S_IRUGO | S_IWUSR, + inv_attr_show, inv_bias_store, ATTR_GYRO_X_OFFSET); +static IIO_DEVICE_ATTR(in_anglvel_y_offset, S_IRUGO | S_IWUSR, + inv_attr_show, inv_bias_store, ATTR_GYRO_Y_OFFSET); +static IIO_DEVICE_ATTR(in_anglvel_z_offset, S_IRUGO | S_IWUSR, + inv_attr_show, inv_bias_store, ATTR_GYRO_Z_OFFSET); + +#ifndef SUPPORT_ONLY_BASIC_FEATURES +static IIO_DEVICE_ATTR(in_step_detector_enable, S_IRUGO | S_IWUSR, + inv_attr_show, inv_basic_attr_store, + ATTR_DMP_STEP_DETECTOR_ON); +static IIO_DEVICE_ATTR(in_step_detector_wake_enable, S_IRUGO | S_IWUSR, + inv_attr_show, inv_basic_attr_store, + ATTR_DMP_STEP_DETECTOR_WAKE_ON); +static IIO_DEVICE_ATTR(in_step_counter_enable, S_IRUGO | S_IWUSR, inv_attr_show, + inv_basic_attr_store, ATTR_DMP_STEP_COUNTER_ON); +static IIO_DEVICE_ATTR(in_step_counter_wake_enable, S_IRUGO | S_IWUSR, + inv_attr_show, inv_basic_attr_store, + ATTR_DMP_STEP_COUNTER_WAKE_ON); + +static IIO_DEVICE_ATTR(event_tilt_enable, S_IRUGO | S_IWUSR, + inv_attr_show, inv_basic_attr_store, + ATTR_DMP_TILT_ENABLE); + +static IIO_DEVICE_ATTR(event_eis_enable, S_IRUGO | S_IWUSR, + inv_attr_show, inv_basic_attr_store, + ATTR_DMP_EIS_ENABLE); + +static IIO_DEVICE_ATTR(event_pick_up_enable, S_IRUGO | S_IWUSR, + inv_attr_show, inv_basic_attr_store, + ATTR_DMP_PICK_UP_ENABLE); + +static IIO_DEVICE_ATTR(in_power_on, S_IRUGO | S_IWUSR, + inv_attr_show, inv_basic_attr_store, + ATTR_IN_POWER_ON); +#endif + +static const struct attribute *inv_raw_attributes[] = { + &dev_attr_debug_reg_dump.attr, + &dev_attr_out_temperature.attr, + &dev_attr_misc_flush_batch.attr, + &dev_attr_misc_self_test.attr, +#ifndef SUPPORT_ONLY_BASIC_FEATURES + &iio_dev_attr_in_power_on.dev_attr.attr, +#endif + &iio_dev_attr_in_accel_enable.dev_attr.attr, + &iio_dev_attr_in_accel_wake_enable.dev_attr.attr, + &iio_dev_attr_info_accel_matrix.dev_attr.attr, + &iio_dev_attr_in_accel_scale.dev_attr.attr, + &iio_dev_attr_misc_batchmode_timeout.dev_attr.attr, + &iio_dev_attr_in_accel_rate.dev_attr.attr, + &iio_dev_attr_in_accel_wake_rate.dev_attr.attr, +}; + +#ifndef SUPPORT_ONLY_BASIC_FEATURES +static const struct attribute *inv_debug_attributes[] = { + &iio_dev_attr_debug_lp_en_off.dev_attr.attr, + &iio_dev_attr_debug_clock_sel.dev_attr.attr, + &iio_dev_attr_debug_reg_write.dev_attr.attr, + &iio_dev_attr_debug_reg_write_addr.dev_attr.attr, +}; +#endif + +static const struct attribute *inv_gyro_attributes[] = { + &iio_dev_attr_info_anglvel_matrix.dev_attr.attr, + &iio_dev_attr_in_anglvel_enable.dev_attr.attr, + &iio_dev_attr_in_anglvel_rate.dev_attr.attr, +#ifndef SUPPORT_ONLY_BASIC_FEATURES + &iio_dev_attr_in_eis_enable.dev_attr.attr, +#endif + &iio_dev_attr_in_anglvel_wake_enable.dev_attr.attr, + &iio_dev_attr_in_anglvel_scale.dev_attr.attr, +#ifndef SUPPORT_ONLY_BASIC_FEATURES + &iio_dev_attr_in_eis_rate.dev_attr.attr, +#endif + &iio_dev_attr_in_anglvel_wake_rate.dev_attr.attr, + &iio_dev_attr_info_gyro_sf.dev_attr.attr, +}; + +static const struct attribute *inv_bias_attributes[] = { + &iio_dev_attr_in_accel_x_st_calibbias.dev_attr.attr, + &iio_dev_attr_in_accel_y_st_calibbias.dev_attr.attr, + &iio_dev_attr_in_accel_z_st_calibbias.dev_attr.attr, + &iio_dev_attr_in_accel_x_offset.dev_attr.attr, + &iio_dev_attr_in_accel_y_offset.dev_attr.attr, + &iio_dev_attr_in_accel_z_offset.dev_attr.attr, + &iio_dev_attr_in_anglvel_x_st_calibbias.dev_attr.attr, + &iio_dev_attr_in_anglvel_y_st_calibbias.dev_attr.attr, + &iio_dev_attr_in_anglvel_z_st_calibbias.dev_attr.attr, + &iio_dev_attr_in_anglvel_x_offset.dev_attr.attr, + &iio_dev_attr_in_anglvel_y_offset.dev_attr.attr, + &iio_dev_attr_in_anglvel_z_offset.dev_attr.attr, +}; + +#ifndef SUPPORT_ONLY_BASIC_FEATURES +static const struct attribute *inv_pedometer_attributes[] = { + &iio_dev_attr_event_tilt_enable.dev_attr.attr, + &iio_dev_attr_event_eis_enable.dev_attr.attr, + &iio_dev_attr_event_pick_up_enable.dev_attr.attr, + &iio_dev_attr_in_step_counter_enable.dev_attr.attr, + &iio_dev_attr_in_step_counter_wake_enable.dev_attr.attr, + &iio_dev_attr_in_step_detector_enable.dev_attr.attr, + &iio_dev_attr_in_step_detector_wake_enable.dev_attr.attr, +}; +#endif + +static struct attribute *inv_attributes[ARRAY_SIZE(inv_raw_attributes) + +#ifndef SUPPORT_ONLY_BASIC_FEATURES + ARRAY_SIZE(inv_debug_attributes) + +#endif + ARRAY_SIZE(inv_gyro_attributes) + + ARRAY_SIZE(inv_bias_attributes) + +#ifndef SUPPORT_ONLY_BASIC_FEATURES + ARRAY_SIZE(inv_pedometer_attributes) + +#endif + + 1]; + +static const struct attribute_group inv_attribute_group = { + .name = "mpu", + .attrs = inv_attributes +}; + +static const struct iio_info mpu_info = { + .driver_module = THIS_MODULE, + .attrs = &inv_attribute_group, +}; + +/* + * inv_check_chip_type() - check and setup chip type. + */ +int inv_check_chip_type(struct iio_dev *indio_dev, const char *name) +{ + int result; + int t_ind; + struct inv_chip_config_s *conf; + struct mpu_platform_data *plat; + struct inv_mpu_state *st; + + st = iio_priv(indio_dev); + conf = &st->chip_config; + plat = &st->plat_data; + + if (!strcmp(name, "iam20680")) + st->chip_type = IAM20680; + else + return -EPERM; + st->chip_config.has_gyro = 1; + + st->hw = &hw_info[st->chip_type]; + result = inv_mpu_initialize(st); + if (result) + return result; + + t_ind = 0; + memcpy(&inv_attributes[t_ind], inv_raw_attributes, + sizeof(inv_raw_attributes)); + t_ind += ARRAY_SIZE(inv_raw_attributes); + +#ifndef SUPPORT_ONLY_BASIC_FEATURES + memcpy(&inv_attributes[t_ind], inv_pedometer_attributes, + sizeof(inv_pedometer_attributes)); + t_ind += ARRAY_SIZE(inv_pedometer_attributes); +#endif + + memcpy(&inv_attributes[t_ind], inv_gyro_attributes, + sizeof(inv_gyro_attributes)); + t_ind += ARRAY_SIZE(inv_gyro_attributes); + + memcpy(&inv_attributes[t_ind], inv_bias_attributes, + sizeof(inv_bias_attributes)); + t_ind += ARRAY_SIZE(inv_bias_attributes); + +#ifndef SUPPORT_ONLY_BASIC_FEATURES + memcpy(&inv_attributes[t_ind], inv_debug_attributes, + sizeof(inv_debug_attributes)); + t_ind += ARRAY_SIZE(inv_debug_attributes); +#endif + + inv_attributes[t_ind] = NULL; + + indio_dev->channels = inv_mpu_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels); + + indio_dev->info = &mpu_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->currentmode = INDIO_DIRECT_MODE; + + return result; +} +EXPORT_SYMBOL_GPL(inv_check_chip_type); + +int inv_create_dmp_sysfs(struct iio_dev *ind) +{ + // dummy + return 0; +} +EXPORT_SYMBOL_GPL(inv_create_dmp_sysfs); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense device ICM20xxx driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_iio_reg_20680.h b/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_iio_reg_20680.h new file mode 100644 index 000000000000..3f8ce71be024 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_iio_reg_20680.h @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2017-2018 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _INV_MPU_IIO_REG_20680_H_ +#define _INV_MPU_IIO_REG_20680_H_ + +/* Uncomment when HAL does not support the algorithm library + * for calibration and sensor fusion not to expose unused + * sysfs entries */ +#define SUPPORT_ONLY_BASIC_FEATURES + +/* Uncomment to read data registers for sensor data instead of FIFO */ +//#define SENSOR_DATA_FROM_REGISTERS + +/* Uncomment to enable timer based batching */ +#define TIMER_BASED_BATCHING + +/* Polling (batch mode) can be enabled only when FIFO read */ +#if defined(SENSOR_DATA_FROM_REGISTERS) +#undef TIMER_BASED_BATCHING +#endif + +/*register and associated bit definition*/ +#define REG_XA_OFFS_H 0x77 +#define REG_YA_OFFS_H 0x7A +#define REG_ZA_OFFS_H 0x7D +#define REG_XG_OFFS_USR_H 0x13 +#define REG_YG_OFFS_USR_H 0x15 +#define REG_ZG_OFFS_USR_H 0x17 +#define REG_SAMPLE_RATE_DIV 0x19 + +#define REG_CONFIG 0x1A +#define EXT_SYNC_SET 8 + +#define REG_GYRO_CONFIG 0x1B +#define BITS_SELF_TEST_EN 0xE0 +#define SHIFT_GYRO_FS_SEL 0x03 + +#define REG_ACCEL_CONFIG 0x1C +#define SHIFT_ACCEL_FS 0x03 + +#define REG_LP_MODE_CTRL 0x1E +#define BIT_GYRO_CYCLE_EN 0x80 + +#define REG_ACCEL_WOM_THR 0x1F +#define REG_ACCEL_WOM_X_THR 0x20 +#define REG_ACCEL_WOM_Y_THR 0x21 +#define REG_ACCEL_WOM_Z_THR 0x22 + +#define REG_ACCEL_MOT_THR 0x1F +#define REG_ACCEL_MOT_DUR 0x20 + +#define REG_ACCEL_CONFIG_2 0x1D +#define BIT_ACCEL_FCHOCIE_B 0x08 + +#define REG_FIFO_EN 0x23 +#define BITS_GYRO_FIFO_EN 0x70 +#define BIT_ACCEL_FIFO_EN 0x08 + +#define REG_FSYNC_INT 0x36 +#define BIT_FSYNC_INT 0x80 + +#define REG_INT_PIN_CFG 0x37 + +#define REG_INT_ENABLE 0x38 +#define BIT_WOM_X_INT_EN 0x80 +#define BIT_WOM_Y_INT_EN 0x40 +#define BIT_WOM_Z_INT_EN 0x20 +#define BIT_WOM_ALL_INT_EN 0xE0 +#define BIT_FSYNC_INT_EN 0x8 +#define BIT_DATA_RDY_EN 0x1 + +#define REG_INT_STATUS 0x3A +#define BIT_WOM_X_INT 0x80 +#define BIT_WOM_Y_INT 0x40 +#define BIT_WOM_Z_INT 0x20 + +#define REG_RAW_ACCEL 0x3B +#define REG_RAW_TEMP 0x41 +#define REG_RAW_GYRO 0x43 +#define REG_EXT_SENS_DATA_00 0x49 +#define REG_EXT_SENS_DATA_08 0x51 +#define REG_EXT_SENS_DATA_09 0x52 + +#define REG_ACCEL_INTEL_CTRL 0x69 +#define BIT_ACCEL_INTEL_EN 0x80 +#define BIT_ACCEL_INTEL_MODE 0x40 + +#define REG_USER_CTRL 0x6A +#define BIT_COND_RST 0x01 +#define BIT_FIFO_RST 0x04 +#define BIT_FIFO_EN 0x40 + +#define REG_PWR_MGMT_1 0x6B +#define BIT_H_RESET 0x80 +#define BIT_SLEEP 0x40 +#define BIT_LP_EN 0x20 +#define BIT_CLK_PLL 0x01 +#define BIT_CLK_MASK 0x07 + +#define REG_PWR_MGMT_2 0x6C +#define BIT_PWR_ACCEL_STBY 0x38 +#define BIT_PWR_GYRO_STBY 0x07 +#define BIT_PWR_ALL_OFF 0x3F +#define BIT_FIFO_LP_EN 0x80 + +#define REG_MEM_BANK_SEL 0x6D +#define REG_MEM_START_ADDR 0x6E +#define REG_MEM_R_W 0x6F + +#define REG_FIFO_COUNT_H 0x72 +#define REG_FIFO_R_W 0x74 +#define REG_WHO_AM_I 0x75 + +#define REG_6500_XG_ST_DATA 0x50 +#define REG_6500_XA_ST_DATA 0xD +#define REG_6500_XA_OFFS_H 0x77 +#define REG_6500_YA_OFFS_H 0x7A +#define REG_6500_ZA_OFFS_H 0x7D +#define REG_6500_ACCEL_CONFIG2 0x1D +#define BIT_ACCEL_FCHOCIE_B 0x08 +#define BIT_FIFO_SIZE_1K 0x40 + +#define REG_LP_MODE_CFG 0x1E + +#define REG_6500_LP_ACCEL_ODR 0x1E +#define REG_6500_ACCEL_WOM_THR 0x1F + +/* data output control reg 2 */ +#define ACCEL_ACCURACY_SET 0x4000 +#define GYRO_ACCURACY_SET 0x2000 +#define CPASS_ACCURACY_SET 0x1000 + +/* data definitions */ +#define ACCEL_COVARIANCE 0 +#define BYTES_PER_SENSOR 6 +#define BYTES_FOR_TEMP 2 +#define FIFO_COUNT_BYTE 2 +#define HARDWARE_FIFO_SIZE 512 +#define FIFO_SIZE (HARDWARE_FIFO_SIZE * 7 / 10) +#define POWER_UP_TIME 100 +#define REG_UP_TIME_USEC 100 +#define LEFT_OVER_BYTES 128 +#define IIO_BUFFER_BYTES 8 +#define BASE_SAMPLE_RATE 1000 +#define DRY_RUN_TIME 50 +#define INV_IAM20680_GYRO_START_TIME 35 +#define INV_IAM20680_ACCEL_START_TIME 30 +#define MODE_1K_INIT_SAMPLE 5 +#define FIRST_SAMPLE_BUF_MS 30 + +#ifdef BIAS_CONFIDENCE_HIGH +#define DEFAULT_ACCURACY 3 +#else +#define DEFAULT_ACCURACY 1 +#endif + +/* temperature */ +#define TEMP_SENSITIVITY 32680 // 326.8 LSB/degC * 100 +#define TEMP_OFFSET 2500 // 25 degC * 100 + +/* enum for sensor */ +enum INV_SENSORS { + SENSOR_ACCEL = 0, + SENSOR_TEMP, + SENSOR_GYRO, + SENSOR_COMPASS, + SENSOR_NUM_MAX, + SENSOR_INVALID, +}; + +enum inv_filter_e { + INV_FILTER_256HZ_NOLPF2 = 0, + INV_FILTER_188HZ, + INV_FILTER_98HZ, + INV_FILTER_42HZ, + INV_FILTER_20HZ, + INV_FILTER_10HZ, + INV_FILTER_5HZ, + INV_FILTER_2100HZ_NOLPF, + NUM_FILTER +}; + +#define MPU_DEFAULT_DMP_FREQ 200 +#define PEDOMETER_FREQ (MPU_DEFAULT_DMP_FREQ >> 2) +#define SENSOR_FUSION_MIN_RATE 100 +#define GESTURE_ACCEL_RATE 50 +#define ESI_GYRO_RATE 1000 +#define MAX_FIFO_PACKET_READ 6 +#define MAX_BATCH_FIFO_SIZE FIFO_SIZE + +#define MIN_MST_ODR_CONFIG 4 +#define MAX_MST_ODR_CONFIG 5 +/* initial rate is important. For non-DMP mode, it is set as 4 1000/256*/ +#define MPU_INIT_SENSOR_RATE 4 +#define MAX_MST_NON_COMPASS_ODR_CONFIG 7 +#define THREE_AXES 3 +#define NINE_ELEM (THREE_AXES * THREE_AXES) +#define MPU_TEMP_SHIFT 16 + +#define DMP_DIVIDER (BASE_SAMPLE_RATE / MPU_DEFAULT_DMP_FREQ) +#define DEFAULT_BATCH_RATE 400 +#define DEFAULT_BATCH_TIME (MSEC_PER_SEC / DEFAULT_BATCH_RATE) + +#define TEMPERATURE_SCALE 3340827L +#define TEMPERATURE_OFFSET 1376256L +#define SECONDARY_INIT_WAIT 100 +#define MPU_SOFT_REV_ADDR 0x86 +#define MPU_SOFT_REV_MASK 0xf +#define SW_REV_LP_EN_MODE 4 + +/* data limit definitions */ +#define MIN_FIFO_RATE 4 +#define MAX_FIFO_RATE MPU_DEFAULT_DMP_FREQ + +#define MAX_MPU_MEM 8192 +#define MAX_PRS_RATE 281 + +enum inv_devices { + ICM20608D, + ICM20690, + ICM20602, + IAM20680, + INV_NUM_PARTS, +}; +#endif diff --git a/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_init_20680.c b/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_init_20680.c new file mode 100644 index 000000000000..58bd8d073890 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_init_20680.c @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2017-2018 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#define pr_fmt(fmt) "inv_mpu: " fmt +#include "../inv_mpu_iio.h" + +static int inv_calc_gyro_sf(s8 pll) +{ + int a, r; + int value, t; + + t = 102870L + 81L * pll; + a = (1L << 30) / t; + r = (1L << 30) - a * t; + value = a * 797 * DMP_DIVIDER; + value += (s64) ((a * 1011387LL * DMP_DIVIDER) >> 20); + value += r * 797L * DMP_DIVIDER / t; + value += (s32) ((s64) ((r * 1011387LL * DMP_DIVIDER) >> 20)) / t; + value <<= 1; + + return value; +} + +static int inv_read_timebase(struct inv_mpu_state *st) +{ + + inv_plat_single_write(st, REG_CONFIG, 3); + + st->eng_info[ENGINE_ACCEL].base_time = NSEC_PER_SEC; + st->eng_info[ENGINE_ACCEL].base_time_1k = NSEC_PER_SEC; + /* talor expansion to calculate base time unit */ + st->eng_info[ENGINE_GYRO].base_time = NSEC_PER_SEC; + st->eng_info[ENGINE_GYRO].base_time_1k = NSEC_PER_SEC; + st->eng_info[ENGINE_I2C].base_time = NSEC_PER_SEC; + st->eng_info[ENGINE_I2C].base_time_1k = NSEC_PER_SEC; + + st->eng_info[ENGINE_ACCEL].orig_rate = BASE_SAMPLE_RATE; + st->eng_info[ENGINE_GYRO].orig_rate = BASE_SAMPLE_RATE; + st->eng_info[ENGINE_I2C].orig_rate = BASE_SAMPLE_RATE; + + st->gyro_sf = inv_calc_gyro_sf(0); + + return 0; +} + +int inv_set_gyro_sf(struct inv_mpu_state *st) +{ + int result; + + result = inv_plat_single_write(st, REG_GYRO_CONFIG, + st->chip_config.fsr << SHIFT_GYRO_FS_SEL); + + return result; +} + +int inv_set_accel_sf(struct inv_mpu_state *st) +{ + int result; + + result = inv_plat_single_write(st, REG_ACCEL_CONFIG, + st->chip_config.accel_fs << SHIFT_ACCEL_FS); + return result; +} + +// dummy for 20602 +int inv_set_accel_intel(struct inv_mpu_state *st) +{ + return 0; +} + +static void inv_init_sensor_struct(struct inv_mpu_state *st) +{ + int i; + + for (i = 0; i < SENSOR_NUM_MAX; i++) + st->sensor[i].rate = MPU_INIT_SENSOR_RATE; + + st->sensor[SENSOR_ACCEL].sample_size = BYTES_PER_SENSOR; + st->sensor[SENSOR_TEMP].sample_size = BYTES_FOR_TEMP; + st->sensor[SENSOR_GYRO].sample_size = BYTES_PER_SENSOR; + + st->sensor_l[SENSOR_L_SIXQ].base = SENSOR_GYRO; + st->sensor_l[SENSOR_L_PEDQ].base = SENSOR_GYRO; + + st->sensor_l[SENSOR_L_SIXQ_WAKE].base = SENSOR_GYRO; + st->sensor_l[SENSOR_L_PEDQ_WAKE].base = SENSOR_GYRO; + + st->sensor[SENSOR_ACCEL].a_en = true; + st->sensor[SENSOR_GYRO].a_en = false; + + st->sensor[SENSOR_ACCEL].g_en = false; + st->sensor[SENSOR_GYRO].g_en = true; + + st->sensor[SENSOR_ACCEL].c_en = false; + st->sensor[SENSOR_GYRO].c_en = false; + + st->sensor[SENSOR_ACCEL].p_en = false; + st->sensor[SENSOR_GYRO].p_en = false; + + st->sensor[SENSOR_ACCEL].engine_base = ENGINE_ACCEL; + st->sensor[SENSOR_GYRO].engine_base = ENGINE_GYRO; + + st->sensor_l[SENSOR_L_ACCEL].base = SENSOR_ACCEL; + st->sensor_l[SENSOR_L_GESTURE_ACCEL].base = SENSOR_ACCEL; + st->sensor_l[SENSOR_L_GYRO].base = SENSOR_GYRO; + st->sensor_l[SENSOR_L_GYRO_CAL].base = SENSOR_GYRO; + st->sensor_l[SENSOR_L_EIS_GYRO].base = SENSOR_GYRO; + + st->sensor_l[SENSOR_L_ACCEL_WAKE].base = SENSOR_ACCEL; + st->sensor_l[SENSOR_L_GYRO_WAKE].base = SENSOR_GYRO; + + st->sensor_l[SENSOR_L_GYRO_CAL_WAKE].base = SENSOR_GYRO; + + st->sensor_l[SENSOR_L_ACCEL].header = ACCEL_HDR; + st->sensor_l[SENSOR_L_GESTURE_ACCEL].header = ACCEL_HDR; + st->sensor_l[SENSOR_L_GYRO].header = GYRO_HDR; + st->sensor_l[SENSOR_L_GYRO_CAL].header = GYRO_CALIB_HDR; + + st->sensor_l[SENSOR_L_EIS_GYRO].header = EIS_GYRO_HDR; + st->sensor_l[SENSOR_L_SIXQ].header = SIXQUAT_HDR; + st->sensor_l[SENSOR_L_THREEQ].header = LPQ_HDR; + st->sensor_l[SENSOR_L_NINEQ].header = NINEQUAT_HDR; + st->sensor_l[SENSOR_L_PEDQ].header = PEDQUAT_HDR; + + st->sensor_l[SENSOR_L_ACCEL_WAKE].header = ACCEL_WAKE_HDR; + st->sensor_l[SENSOR_L_GYRO_WAKE].header = GYRO_WAKE_HDR; + st->sensor_l[SENSOR_L_GYRO_CAL_WAKE].header = GYRO_CALIB_WAKE_HDR; + st->sensor_l[SENSOR_L_MAG_WAKE].header = COMPASS_WAKE_HDR; + st->sensor_l[SENSOR_L_MAG_CAL_WAKE].header = COMPASS_CALIB_WAKE_HDR; + st->sensor_l[SENSOR_L_SIXQ_WAKE].header = SIXQUAT_WAKE_HDR; + st->sensor_l[SENSOR_L_NINEQ_WAKE].header = NINEQUAT_WAKE_HDR; + st->sensor_l[SENSOR_L_PEDQ_WAKE].header = PEDQUAT_WAKE_HDR; + + st->sensor_l[SENSOR_L_ACCEL].wake_on = false; + st->sensor_l[SENSOR_L_GYRO].wake_on = false; + st->sensor_l[SENSOR_L_GYRO_CAL].wake_on = false; + st->sensor_l[SENSOR_L_MAG].wake_on = false; + st->sensor_l[SENSOR_L_MAG_CAL].wake_on = false; + st->sensor_l[SENSOR_L_EIS_GYRO].wake_on = false; + st->sensor_l[SENSOR_L_SIXQ].wake_on = false; + st->sensor_l[SENSOR_L_NINEQ].wake_on = false; + st->sensor_l[SENSOR_L_PEDQ].wake_on = false; + + st->sensor_l[SENSOR_L_ACCEL_WAKE].wake_on = true; + st->sensor_l[SENSOR_L_GYRO_WAKE].wake_on = true; + st->sensor_l[SENSOR_L_GYRO_CAL_WAKE].wake_on = true; + st->sensor_l[SENSOR_L_MAG_WAKE].wake_on = true; + st->sensor_l[SENSOR_L_SIXQ_WAKE].wake_on = true; + st->sensor_l[SENSOR_L_NINEQ_WAKE].wake_on = true; + st->sensor_l[SENSOR_L_PEDQ_WAKE].wake_on = true; +} + +static int inv_init_config(struct inv_mpu_state *st) +{ + int res, i; + + st->batch.overflow_on = 0; + st->chip_config.fsr = MPU_INIT_GYRO_SCALE; + st->chip_config.accel_fs = MPU_INIT_ACCEL_SCALE; + st->ped.int_thresh = MPU_INIT_PED_INT_THRESH; + st->ped.step_thresh = MPU_INIT_PED_STEP_THRESH; + st->chip_config.low_power_gyro_on = 1; + st->eis.count_precision = NSEC_PER_MSEC; + st->firmware = 0; + st->fifo_count_mode = BYTE_MODE; +#ifdef TIMER_BASED_BATCHING + st->batch_timeout = 0; + st->is_batch_timer_running = false; +#endif + + st->eng_info[ENGINE_GYRO].base_time = NSEC_PER_SEC; + st->eng_info[ENGINE_ACCEL].base_time = NSEC_PER_SEC; + + inv_init_sensor_struct(st); + res = inv_read_timebase(st); + if (res) + return res; + + res = inv_set_gyro_sf(st); + if (res) + return res; + res = inv_set_accel_sf(st); + if (res) + return res; + res = inv_set_accel_intel(st); + if (res) + return res; + + for (i = 0; i < SENSOR_NUM_MAX; i++) + st->sensor[i].ts = 0; + + for (i = 0; i < SENSOR_NUM_MAX; i++) + st->sensor[i].previous_ts = 0; + + return res; +} + +int inv_mpu_initialize(struct inv_mpu_state *st) +{ + u8 v; + int result; + struct inv_chip_config_s *conf; + struct mpu_platform_data *plat; + + conf = &st->chip_config; + plat = &st->plat_data; + + /* verify whoami */ + result = inv_plat_read(st, REG_WHO_AM_I, 1, &v); + if (result) + return result; + pr_info("whoami= %x\n", v); + if (v == 0x00 || v == 0xff) + return -ENODEV; + + /* reset to make sure previous state are not there */ + result = inv_plat_single_write(st, REG_PWR_MGMT_1, BIT_H_RESET); + if (result) + return result; + usleep_range(REG_UP_TIME_USEC, REG_UP_TIME_USEC); + msleep(100); + /* toggle power state */ + result = inv_set_power(st, false); + if (result) + return result; + result = inv_set_power(st, true); + if (result) + return result; + + result = inv_plat_single_write(st, REG_USER_CTRL, st->i2c_dis); + if (result) + return result; + result = inv_init_config(st); + if (result) + return result; + + result = mem_r(MPU_SOFT_REV_ADDR, 1, &v); + pr_info("sw_rev=%x, res=%d\n", v, result); + if (result) + return result; + st->chip_config.lp_en_mode_off = 0; + + pr_info("%s: Mask %X, v = %X, lp mode = %d\n", __func__, + MPU_SOFT_REV_MASK, v, st->chip_config.lp_en_mode_off); + result = inv_set_power(st, false); + + pr_info("%s: initialize result is %d....\n", __func__, result); + return 0; +} diff --git a/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_parsing_20680.c b/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_parsing_20680.c new file mode 100644 index 000000000000..0f17b6dd2c52 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_parsing_20680.c @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2017-2018 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#define pr_fmt(fmt) "inv_mpu: " fmt + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/sysfs.h> +#include <linux/jiffies.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/poll.h> +#include <linux/miscdevice.h> +#include <linux/math64.h> + +#include "../inv_mpu_iio.h" + +static char iden[] = { 1, 0, 0, 0, 1, 0, 0, 0, 1 }; + +static int inv_process_gyro(struct inv_mpu_state *st, u8 *d, u64 t) +{ + s16 raw[3]; + s32 calib[3]; + int i; +#define BIAS_UNIT 2859 + + for (i = 0; i < 3; i++) + raw[i] = be16_to_cpup((__be16 *) (d + i * 2)); + + for (i = 0; i < 3; i++) + calib[i] = (raw[i] << 15); + + + inv_push_gyro_data(st, raw, calib, t); + + return 0; +} + +static int inv_check_fsync(struct inv_mpu_state *st, u8 fsync_status) +{ + u8 data[1]; + + if (!st->chip_config.eis_enable) + return 0; + inv_plat_read(st, REG_FSYNC_INT, 1, data); + if (data[0] & BIT_FSYNC_INT) { + pr_debug("fsync\n"); + st->eis.eis_triggered = true; + st->eis.fsync_delay = 1; + st->eis.prev_state = 1; + st->eis.frame_count++; + st->eis.eis_frame = true; + } + st->header_count--; + + return 0; +} + +static int inv_push_sensor(struct inv_mpu_state *st, int ind, u64 t, u8 *d) +{ +#ifdef ACCEL_BIAS_TEST + s16 acc[3], avg[3]; +#endif + + switch (ind) { + case SENSOR_ACCEL: + inv_convert_and_push_8bytes(st, ind, d, t, iden); +#ifdef ACCEL_BIAS_TEST + acc[0] = be16_to_cpup((__be16 *) (d)); + acc[1] = be16_to_cpup((__be16 *) (d + 2)); + acc[2] = be16_to_cpup((__be16 *) (d + 4)); + if(inv_get_3axis_average(acc, avg, 0)){ + pr_debug("accel 200 samples average = %5d, %5d, %5d\n", avg[0], avg[1], avg[2]); + } +#endif + break; + case SENSOR_TEMP: + inv_check_fsync(st, d[1]); + break; + case SENSOR_GYRO: + inv_process_gyro(st, d, t); + break; + default: + break; + } + + return 0; +} + +static int inv_push_20680_data(struct inv_mpu_state *st, u8 *d) +{ + u8 *dptr; + int i; + + dptr = d; + + for (i = 0; i < SENSOR_NUM_MAX; i++) { + if (st->sensor[i].on) { + inv_get_dmp_ts(st, i); + if (st->sensor[i].send && (!st->ts_algo.first_sample)) { + st->sensor[i].sample_calib++; + inv_push_sensor(st, i, st->sensor[i].ts, dptr); + } + dptr += st->sensor[i].sample_size; + } + } + if (st->ts_algo.first_sample) + st->ts_algo.first_sample--; + st->header_count--; + + return 0; +} + +static int inv_process_20680_data(struct inv_mpu_state *st) +{ + int total_bytes, tmp, res, fifo_count, pk_size, i; + u8 *dptr, *d; + u8 data[14]; + bool done_flag; + u8 v; +#ifdef SENSOR_DATA_FROM_REGISTERS + u8 reg; + int len; +#endif + + if(st->gesture_only_on && (!st->batch.timeout)) { + res = inv_plat_read(st, REG_INT_STATUS, 1, data); + if (res) + return res; + pr_debug("ges cnt=%d, statu=%x\n", + st->gesture_int_count, data[0]); + if (data[0] & (BIT_WOM_ALL_INT_EN)) { + if (!st->gesture_int_count) { + inv_switch_power_in_lp(st, true); + res = inv_plat_single_write(st, REG_INT_ENABLE, + BIT_WOM_ALL_INT_EN | BIT_DATA_RDY_EN); + if (res) + return res; + v = 0; + if (st->chip_config.gyro_enable) + v |= BITS_GYRO_FIFO_EN; + + if (st->chip_config.accel_enable) + v |= BIT_ACCEL_FIFO_EN; + res = inv_plat_single_write(st, REG_FIFO_EN, v); + if (res) + return res; + /* First time wake up from WOM. + We don't need data in the FIFO */ + res = inv_reset_fifo(st, true); + if (res) + return res; + res = inv_switch_power_in_lp(st, false); + st->gesture_int_count = WOM_DELAY_THRESHOLD; + + return res; + } + st->gesture_int_count = WOM_DELAY_THRESHOLD; + } else { + if (!st->gesture_int_count) { + inv_switch_power_in_lp(st, true); + res = inv_plat_single_write(st, REG_FIFO_EN, 0); + res = inv_plat_single_write(st, REG_INT_ENABLE, + BIT_WOM_ALL_INT_EN); + inv_switch_power_in_lp(st, false); + + return res; + } + st->gesture_int_count--; + } + } + + fifo_count = inv_get_last_run_time_non_dmp_record_mode(st); + pr_debug("fifc= %d\n", fifo_count); + if (!fifo_count) { + pr_debug("REG_FIFO_COUNT_H size is 0\n"); + return 0; + } + pk_size = st->batch.pk_size; + if (!pk_size) + return -EINVAL; + + if (fifo_count >= (HARDWARE_FIFO_SIZE / st->batch.pk_size)) { + pr_warn("fifo overflow pkt count=%d pkt sz=%d\n", fifo_count, st->batch.pk_size); + return -EOVERFLOW; + } + + fifo_count *= st->batch.pk_size; + st->fifo_count = fifo_count; + d = st->fifo_data_store; + dptr = d; + total_bytes = fifo_count; + +#ifdef SENSOR_DATA_FROM_REGISTERS + len = 0; + if (st->sensor[SENSOR_GYRO].on) { + reg = REG_RAW_GYRO; + len += BYTES_PER_SENSOR; + if (st->sensor[SENSOR_ACCEL].on && !st->sensor[SENSOR_TEMP].on) + len += BYTES_FOR_TEMP; + } + if (st->sensor[SENSOR_TEMP].on) { + reg = REG_RAW_TEMP; + len += BYTES_FOR_TEMP; + } + if (st->sensor[SENSOR_ACCEL].on) { + reg = REG_RAW_ACCEL; + len += BYTES_PER_SENSOR; + } + + if (len == 0) { + pr_debug("No sensor is enabled\n"); + return 0; + } + + /* read data registers */ + res = inv_plat_read(st, reg, len, data); + if (res < 0) { + pr_err("read data registers is failed\n"); + return res; + } + + /* copy sensor data to buffer as FIFO data format */ + tmp = 0; + if (st->sensor[SENSOR_ACCEL].on) { + for (i = 0; i < BYTES_PER_SENSOR; i++) + dptr[i] = data[tmp + i]; + dptr += BYTES_PER_SENSOR; + tmp += BYTES_PER_SENSOR; + } + + if (st->sensor[SENSOR_TEMP].on) { + for (i = 0; i < BYTES_FOR_TEMP; i++) + dptr[i] = data[tmp + i]; + dptr += BYTES_FOR_TEMP; + tmp += BYTES_FOR_TEMP; + } + + if (st->sensor[SENSOR_GYRO].on) { + if (st->sensor[SENSOR_ACCEL].on && !st->sensor[SENSOR_TEMP].on) + tmp += BYTES_FOR_TEMP; + for (i = 0; i < BYTES_PER_SENSOR; i++) + dptr[i] = data[tmp + i]; + } +#else + while (total_bytes > 0) { + if (total_bytes < pk_size * MAX_FIFO_PACKET_READ) + tmp = total_bytes; + else + tmp = pk_size * MAX_FIFO_PACKET_READ; + res = inv_plat_read(st, REG_FIFO_R_W, tmp, dptr); + if (res < 0) { + pr_err("read REG_FIFO_R_W is failed\n"); + return res; + } + pr_debug("inside: %x, %x, %x, %x, %x, %x, %x, %x\n", dptr[0], dptr[1], dptr[2], + dptr[3], dptr[4], dptr[5], dptr[6], dptr[7]); + pr_debug("insid2: %x, %x, %x, %x, %x, %x, %x, %x\n", dptr[8], dptr[9], dptr[10], + dptr[11], dptr[12], dptr[13], dptr[14], dptr[15]); + + dptr += tmp; + total_bytes -= tmp; + } +#endif /* SENSOR_DATA_FROM_REGISTERS */ + dptr = d; + pr_debug("dd: %x, %x, %x, %x, %x, %x, %x, %x\n", d[0], d[1], d[2], + d[3], d[4], d[5], d[6], d[7]); + pr_debug("dd2: %x, %x, %x, %x, %x, %x, %x, %x\n", d[8], d[9], d[10], + d[11], d[12], d[13], d[14], d[15]); + total_bytes = fifo_count; + + for (i = 0; i < SENSOR_NUM_MAX; i++) { + if (st->sensor[i].on) { + st->sensor[i].count = total_bytes / pk_size; + } + } + st->header_count = 0; + for (i = 0; i < SENSOR_NUM_MAX; i++) { + if (st->sensor[i].on) + st->header_count = max(st->header_count, + st->sensor[i].count); + } + + st->ts_algo.calib_counter++; + inv_bound_timestamp(st); + + dptr = d; + done_flag = false; + + while (!done_flag) { + pr_debug("total%d, pk=%d\n", total_bytes, pk_size); + if (total_bytes >= pk_size) { + res = inv_push_20680_data(st, dptr); + if (res) + return res; + total_bytes -= pk_size; + dptr += pk_size; + } else { + done_flag = true; + } + } + + return 0; +} + +/* + * _inv_read_fifo() - Transfer data from FIFO to ring buffer. + */ +static void _inv_read_fifo(struct inv_mpu_state *st) +{ + struct iio_dev *indio_dev = iio_priv_to_dev(st); + int result; + + result = wait_event_interruptible_timeout(st->wait_queue, + st->resume_state, msecs_to_jiffies(300)); + if (result <= 0) + return; + mutex_lock(&indio_dev->mlock); +#ifdef TIMER_BASED_BATCHING + if (st->batch_timeout) { + if (inv_plat_single_write(st, REG_INT_ENABLE, st->int_en)) + pr_err("REG_INT_ENABLE write error\n"); + } +#endif + st->wake_sensor_received = false; + result = inv_process_20680_data(st); + if (result) + goto err_reset_fifo; + mutex_unlock(&indio_dev->mlock); + + if (st->wake_sensor_received) +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_timeout(&st->wake_lock, msecs_to_jiffies(200)); +#else + __pm_wakeup_event(&st->wake_lock, 200); /* 200 msecs */ +#endif + return; + +err_reset_fifo: + if ((!st->chip_config.gyro_enable) && + (!st->chip_config.accel_enable) && + (!st->chip_config.slave_enable) && + (!st->chip_config.pressure_enable)) { + inv_switch_power_in_lp(st, false); + mutex_unlock(&indio_dev->mlock); + + return; + } + + pr_err("error to reset fifo\n"); + inv_switch_power_in_lp(st, true); + inv_reset_fifo(st, true); + inv_switch_power_in_lp(st, false); + mutex_unlock(&indio_dev->mlock); + + return; +} + +irqreturn_t inv_read_fifo(int irq, void *dev_id) +{ + struct inv_mpu_state *st = (struct inv_mpu_state *)dev_id; + + _inv_read_fifo(st); + + return IRQ_HANDLED; +} + +#ifdef TIMER_BASED_BATCHING +void inv_batch_work(struct work_struct *work) +{ + struct inv_mpu_state *st = + container_of(work, struct inv_mpu_state, batch_work); + struct iio_dev *indio_dev = iio_priv_to_dev(st); + + mutex_lock(&indio_dev->mlock); + if (inv_plat_single_write(st, REG_INT_ENABLE, st->int_en | BIT_DATA_RDY_EN)) + pr_err("REG_INT_ENABLE write error\n"); + mutex_unlock(&indio_dev->mlock); + + return; +} +#endif + +int inv_flush_batch_data(struct iio_dev *indio_dev, int data) +{ + struct inv_mpu_state *st = iio_priv(indio_dev); + +#ifndef SENSOR_DATA_FROM_REGISTERS + if (st->chip_config.gyro_enable || + st->chip_config.accel_enable || + st->chip_config.slave_enable || + st->chip_config.pressure_enable) { + st->wake_sensor_received = 0; + inv_process_20680_data(st); + if (st->wake_sensor_received) +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_timeout(&st->wake_lock, msecs_to_jiffies(200)); +#else + __pm_wakeup_event(&st->wake_lock, 200); /* 200 msecs */ +#endif + inv_switch_power_in_lp(st, false); + } +#endif /* SENSOR_DATA_FROM_REGISTERS */ + inv_push_marker_to_buffer(st, END_MARKER, data); + + return 0; +} + diff --git a/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_selftest_20680.c b/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_selftest_20680.c new file mode 100644 index 000000000000..7a90b4d8b882 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_selftest_20680.c @@ -0,0 +1,752 @@ +/* +* Copyright (C) 2017-2018 InvenSense, Inc. +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#define pr_fmt(fmt) "inv_mpu: " fmt + +#include "../inv_mpu_iio.h" + +/* register settings */ +#define DEF_SELFTEST_GYRO_SENS (32768 / 250) +/* wait time before collecting data */ +#define MAX_PACKETS 20 +#define SELFTEST_WAIT_TIME (MAX_PACKETS * 10) +#define DEF_ST_STABLE_TIME 20 +#define DEF_GYRO_SCALE 131 +#define DEF_ST_PRECISION 1000 +#define DEF_ST_ACCEL_FS_MG 2000UL +#define DEF_ST_SCALE 32768 +#define DEF_ST_TRY_TIMES 2 +#define DEF_ST_ACCEL_RESULT_SHIFT 1 +#define DEF_ST_SAMPLES 200 + +#define DEF_ACCEL_ST_SHIFT_DELTA_MIN 500 +#define DEF_ACCEL_ST_SHIFT_DELTA_MAX 1500 +#define DEF_GYRO_CT_SHIFT_DELTA 500 + +#define SENSOR_UP_TIME 30 +#define REG_UP_TIME 2 + +#define DEF_ST_ACCEL_FS_MG 2000UL +#define DEF_ACCEL_ST_SHIFT_DELTA 500 +#define ACCEL_ST_AL_MIN ((DEF_ACCEL_ST_AL_MIN * DEF_ST_SCALE \ + / DEF_ST_ACCEL_FS_MG) * DEF_ST_PRECISION) +#define ACCEL_ST_AL_MAX ((DEF_ACCEL_ST_AL_MAX * DEF_ST_SCALE \ + / DEF_ST_ACCEL_FS_MG) * DEF_ST_PRECISION) + +#define THREE_AXIS 3 +#define DEF_ST_MPU6500_ACCEL_LPF 2 +#define DEF_SELFTEST_SAMPLE_RATE 0 /* 1000Hz */ +#define DEF_SELFTEST_SAMPLE_RATE_LP 3 /* 250Hz */ +#define DEF_SELFTEST_SAMPLE_RATE_ACC_LP 10 /* 250Hz LPOSC_CLKSEL */ +#define INV_MPU_SAMPLE_RATE_CHANGE_STABLE 50 +#define DEF_SELFTEST_6500_ACCEL_FS (0 << 3) +#define DEF_SELFTEST_GYRO_FS (0 << 3) +#define DEF_ST_6500_STABLE_TIME 20 +#define BIT_ACCEL_OUT 0x08 +#define BITS_GYRO_OUT 0x70 +#define THREE_AXIS 3 +#define DEF_GYRO_WAIT_TIME 10 +#define DEF_GYRO_WAIT_TIME_LP 50 + +/* Gyro Offset Max Value (dps) */ +#define DEF_GYRO_OFFSET_MAX 20 +/* Gyro Self Test Absolute Limits ST_AL (dps) */ +#define DEF_GYRO_ST_AL 60 +/* Accel Self Test Absolute Limits ST_AL (mg) */ +#define DEF_ACCEL_ST_AL_MIN 225 +#define DEF_ACCEL_ST_AL_MAX 675 + +struct recover_regs { + u8 int_enable; /* REG_INT_ENABLE */ + u8 fifo_en; /* REG_FIFO_EN */ + u8 user_ctrl; /* REG_USER_CTRL */ + u8 config; /* REG_CONFIG */ + u8 gyro_config; /* REG_GYRO_CONFIG */ + u8 accel_config; /* REG_ACCEL_CONFIG */ + u8 accel_config_2; /* REG_ACCEL_CONFIG_2 */ + u8 smplrt_div; /* REG_SAMPLE_RATE_DIV */ + u8 lp_mode; /* REG_LP_MODE_CTRL */ + u8 pwr_mgmt_1; /* REG_PWR_MGMT_1 */ + u8 pwr_mgmt_2; /* REG_PWR_MGMT_2 */ +}; + +static struct recover_regs saved_regs; + +static const u16 mpu_st_tb[256] = { + 2620, 2646, 2672, 2699, 2726, 2753, 2781, 2808, + 2837, 2865, 2894, 2923, 2952, 2981, 3011, 3041, + 3072, 3102, 3133, 3165, 3196, 3228, 3261, 3293, + 3326, 3359, 3393, 3427, 3461, 3496, 3531, 3566, + 3602, 3638, 3674, 3711, 3748, 3786, 3823, 3862, + 3900, 3939, 3979, 4019, 4059, 4099, 4140, 4182, + 4224, 4266, 4308, 4352, 4395, 4439, 4483, 4528, + 4574, 4619, 4665, 4712, 4759, 4807, 4855, 4903, + 4953, 5002, 5052, 5103, 5154, 5205, 5257, 5310, + 5363, 5417, 5471, 5525, 5581, 5636, 5693, 5750, + 5807, 5865, 5924, 5983, 6043, 6104, 6165, 6226, + 6289, 6351, 6415, 6479, 6544, 6609, 6675, 6742, + 6810, 6878, 6946, 7016, 7086, 7157, 7229, 7301, + 7374, 7448, 7522, 7597, 7673, 7750, 7828, 7906, + 7985, 8065, 8145, 8227, 8309, 8392, 8476, 8561, + 8647, 8733, 8820, 8909, 8998, 9088, 9178, 9270, + 9363, 9457, 9551, 9647, 9743, 9841, 9939, 10038, + 10139, 10240, 10343, 10446, 10550, 10656, 10763, 10870, + 10979, 11089, 11200, 11312, 11425, 11539, 11654, 11771, + 11889, 12008, 12128, 12249, 12371, 12495, 12620, 12746, + 12874, 13002, 13132, 13264, 13396, 13530, 13666, 13802, + 13940, 14080, 14221, 14363, 14506, 14652, 14798, 14946, + 15096, 15247, 15399, 15553, 15709, 15866, 16024, 16184, + 16346, 16510, 16675, 16842, 17010, 17180, 17352, 17526, + 17701, 17878, 18057, 18237, 18420, 18604, 18790, 18978, + 19167, 19359, 19553, 19748, 19946, 20145, 20347, 20550, + 20756, 20963, 21173, 21385, 21598, 21814, 22033, 22253, + 22475, 22700, 22927, 23156, 23388, 23622, 23858, 24097, + 24338, 24581, 24827, 25075, 25326, 25579, 25835, 26093, + 26354, 26618, 26884, 27153, 27424, 27699, 27976, 28255, + 28538, 28823, 29112, 29403, 29697, 29994, 30294, 30597, + 30903, 31212, 31524, 31839, 32157, 32479, 32804 +}; + +static void inv_show_saved_setting(struct inv_mpu_state *st) +{ + pr_debug(" REG_INT_ENABLE : 0x%02X\n", saved_regs.int_enable); + pr_debug(" REG_FIFO_EN : 0x%02X\n", saved_regs.fifo_en); + pr_debug(" REG_USER_CTRL : 0x%02X\n", saved_regs.user_ctrl); + pr_debug(" REG_CONFIG : 0x%02X\n", saved_regs.config); + pr_debug(" REG_GYRO_CONFIG : 0x%02X\n", saved_regs.gyro_config); + pr_debug(" REG_ACCEL_CONFIG : 0x%02X\n", saved_regs.accel_config); + pr_debug(" REG_ACCEL_CONFIG_2 : 0x%02X\n", saved_regs.accel_config_2); + pr_debug(" REG_SAMPLE_RATE_DIV : 0x%02X\n", saved_regs.smplrt_div); + pr_debug(" REG_LP_MODE_CTRL : 0x%02X\n", saved_regs.lp_mode); + pr_debug(" REG_PWR_MGMT_1 : 0x%02X\n", saved_regs.pwr_mgmt_1); + pr_debug(" REG_PWR_MGMT_2 : 0x%02X\n", saved_regs.pwr_mgmt_2); +} + +static int inv_save_setting(struct inv_mpu_state *st) +{ + int result; + + result = inv_plat_read(st, REG_PWR_MGMT_1, 1, + &saved_regs.pwr_mgmt_1); + if (result) + return result; + + /* wake up */ + result = inv_plat_single_write(st, REG_PWR_MGMT_1, + (saved_regs.pwr_mgmt_1 & ~BIT_SLEEP)); + if (result) + return result; + + result = inv_plat_read(st, REG_INT_ENABLE, 1, + &saved_regs.int_enable); + if (result) + return result; + result = inv_plat_read(st, REG_FIFO_EN, 1, + &saved_regs.fifo_en); + if (result) + return result; + result = inv_plat_read(st, REG_USER_CTRL, 1, + &saved_regs.user_ctrl); + if (result) + return result; + result = inv_plat_read(st, REG_CONFIG, 1, + &saved_regs.config); + if (result) + return result; + result = inv_plat_read(st, REG_GYRO_CONFIG, 1, + &saved_regs.gyro_config); + if (result) + return result; + result = inv_plat_read(st, REG_ACCEL_CONFIG, 1, + &saved_regs.accel_config); + if (result) + return result; + result = inv_plat_read(st, REG_ACCEL_CONFIG_2, 1, + &saved_regs.accel_config_2); + if (result) + return result; + result = inv_plat_read(st, REG_SAMPLE_RATE_DIV, 1, + &saved_regs.smplrt_div); + if (result) + return result; + result = inv_plat_read(st, REG_LP_MODE_CTRL, 1, + &saved_regs.lp_mode); + if (result) + return result; + result = inv_plat_read(st, REG_PWR_MGMT_2, 1, + &saved_regs.pwr_mgmt_2); + if (result) + return result; + + inv_show_saved_setting(st); + + return result; +} + +static int inv_recover_setting(struct inv_mpu_state *st) +{ + int result; + /* Stop sensors */ + result = inv_plat_single_write(st, REG_PWR_MGMT_2, + BIT_PWR_ACCEL_STBY | BIT_PWR_GYRO_STBY); + if (result) + return result; + + /* Restore sensor configurations */ + result = inv_plat_single_write(st, REG_INT_ENABLE, + saved_regs.int_enable); + if (result) + return result; + result = inv_plat_single_write(st, REG_FIFO_EN, + saved_regs.fifo_en); + if (result) + return result; + result = inv_plat_single_write(st, REG_USER_CTRL, + saved_regs.user_ctrl); + if (result) + return result; + result = inv_plat_single_write(st, REG_CONFIG, + saved_regs.config); + if (result) + return result; + result = inv_plat_single_write(st, REG_GYRO_CONFIG, + saved_regs.gyro_config); + if (result) + return result; + result = inv_plat_single_write(st, REG_ACCEL_CONFIG, + saved_regs.accel_config); + if (result) + return result; + result = inv_plat_single_write(st, REG_ACCEL_CONFIG_2, + saved_regs.accel_config_2); + if (result) + return result; + result = inv_plat_single_write(st, REG_SAMPLE_RATE_DIV, + saved_regs.smplrt_div); + if (result) + return result; + result = inv_plat_single_write(st, REG_LP_MODE_CTRL, + saved_regs.lp_mode); + if (result) + return result; + result = inv_plat_single_write(st, REG_PWR_MGMT_1, + saved_regs.pwr_mgmt_1); + if (result) + return result; + + result = inv_plat_single_write(st, REG_PWR_MGMT_2, + saved_regs.pwr_mgmt_2); + if (result) + return result; + + return result; +} + +int inv_switch_engine(struct inv_mpu_state *st, bool en, u32 mask) +{ + u8 data, mgmt_1; + int result; + + if (BIT_PWR_GYRO_STBY == mask) { + result = inv_plat_read(st, REG_PWR_MGMT_1, 1, &mgmt_1); + if (result) + return result; + mgmt_1 &= ~BIT_CLK_MASK; + } + + if ((BIT_PWR_GYRO_STBY == mask) && (!en)) { + result = inv_plat_single_write(st, REG_PWR_MGMT_1, mgmt_1); + if (result) + return result; + } + + result = inv_plat_read(st, REG_PWR_MGMT_2, 1, &data); + if (result) + return result; + if (en) + data &= (~mask); + else + data |= mask; + data |= BIT_FIFO_LP_EN; + result = inv_plat_single_write(st, REG_PWR_MGMT_2, data); + if (result) + return result; + + if ((BIT_PWR_GYRO_STBY == mask) && en) { + /* only gyro on needs sensor up time */ + msleep(SENSOR_UP_TIME); + /* after gyro is on & stable, switch internal clock to PLL */ + mgmt_1 |= BIT_CLK_PLL; + result = inv_plat_single_write(st, REG_PWR_MGMT_1, mgmt_1); + if (result) + return result; + } + if ((BIT_PWR_ACCEL_STBY == mask) && en) + msleep(REG_UP_TIME); + + return 0; +} + +int inv_set_offset_reg(struct inv_mpu_state *st, int reg, int val) +{ + int result; + u8 d; + + d = ((val >> 8) & 0xff); + result = inv_plat_single_write(st, reg, d); + if (result) + return result; + + d = (val & 0xff); + result = inv_plat_single_write(st, reg + 1, d); + + return result; +} + +/** +* inv_check_gyro_self_test() - check gyro self test. this function +* returns zero as success. A non-zero return +* value indicates failure in self test. +* @*st: main data structure. +* @*reg_avg: average value of normal test. +* @*st_avg: average value of self test +*/ +int inv_check_gyro_self_test(struct inv_mpu_state *st, + int *reg_avg, int *st_avg) { + u8 regs[3]; + int ret_val, result; + int otp_value_zero = 0; + int st_shift_prod[3], st_shift_cust[3], i; + + ret_val = 0; + result = inv_plat_read(st, REG_6500_XG_ST_DATA, 3, regs); + if (result) + return result; + pr_debug("%s self_test gyro shift_code - %02x %02x %02x\n", + st->hw->name, regs[0], regs[1], regs[2]); + + for (i = 0; i < 3; i++) { + if (regs[i] != 0) { + st_shift_prod[i] = mpu_st_tb[regs[i] - 1]; + } else { + st_shift_prod[i] = 0; + otp_value_zero = 1; + } + } + pr_debug("%s self_test gyro st_shift_prod - %+d %+d %+d\n", + st->hw->name, st_shift_prod[0], st_shift_prod[1], + st_shift_prod[2]); + + for (i = 0; i < 3; i++) { + st_shift_cust[i] = st_avg[i] - reg_avg[i]; + if (!otp_value_zero) { + /* Self Test Pass/Fail Criteria A */ + if (st_shift_cust[i] < DEF_GYRO_CT_SHIFT_DELTA + * st_shift_prod[i]) + ret_val = 1; + } else { + /* Self Test Pass/Fail Criteria B */ + if (st_shift_cust[i] < DEF_GYRO_ST_AL * + DEF_SELFTEST_GYRO_SENS * + DEF_ST_PRECISION) + ret_val = 1; + } + } + pr_debug("%s self_test gyro st_shift_cust - %+d %+d %+d\n", + st->hw->name, st_shift_cust[0], st_shift_cust[1], + st_shift_cust[2]); + + if (ret_val == 0) { + /* Self Test Pass/Fail Criteria C */ + for (i = 0; i < 3; i++) + if (abs(reg_avg[i]) > DEF_GYRO_OFFSET_MAX * + DEF_SELFTEST_GYRO_SENS * + DEF_ST_PRECISION) + ret_val = 1; + } + + return ret_val; +} + +/** +* inv_check_accel_self_test() - check 6500 accel self test. this function +* returns zero as success. A non-zero return +* value indicates failure in self test. +* @*st: main data structure. +* @*reg_avg: average value of normal test. +* @*st_avg: average value of self test +*/ +int inv_check_accel_self_test(struct inv_mpu_state *st, + int *reg_avg, int *st_avg) { + int ret_val, result; + int st_shift_prod[3], st_shift_cust[3], st_shift_ratio[3], i; + u8 regs[3]; + int otp_value_zero = 0; + + ret_val = 0; + result = inv_plat_read(st, REG_6500_XA_ST_DATA, 3, regs); + if (result) + return result; + pr_debug("%s self_test accel shift_code - %02x %02x %02x\n", + st->hw->name, regs[0], regs[1], regs[2]); + + for (i = 0; i < 3; i++) { + if (regs[i] != 0) { + st_shift_prod[i] = mpu_st_tb[regs[i] - 1]; + } else { + st_shift_prod[i] = 0; + otp_value_zero = 1; + } + } + pr_debug("%s self_test accel st_shift_prod - %+d %+d %+d\n", + st->hw->name, st_shift_prod[0], st_shift_prod[1], + st_shift_prod[2]); + + if (!otp_value_zero) { + /* Self Test Pass/Fail Criteria A */ + for (i = 0; i < 3; i++) { + st_shift_cust[i] = st_avg[i] - reg_avg[i]; + st_shift_ratio[i] = abs(st_shift_cust[i] / + st_shift_prod[i] - DEF_ST_PRECISION); + if (st_shift_ratio[i] > DEF_ACCEL_ST_SHIFT_DELTA) + ret_val = 1; + } + } else { + /* Self Test Pass/Fail Criteria B */ + for (i = 0; i < 3; i++) { + st_shift_cust[i] = abs(st_avg[i] - reg_avg[i]); + if (st_shift_cust[i] < ACCEL_ST_AL_MIN || + st_shift_cust[i] > ACCEL_ST_AL_MAX) + ret_val = 1; + } + } + pr_debug("%s self_test accel st_shift_cust - %+d %+d %+d\n", + st->hw->name, st_shift_cust[0], st_shift_cust[1], + st_shift_cust[2]); + + return ret_val; +} + +/* + * inv_do_test() - do the actual test of self testing + */ +int inv_do_test(struct inv_mpu_state *st, int self_test_flag, + int *gyro_result, int *accel_result, int lp_mode) +{ + int result, i, j, packet_size; + u8 data[BYTES_PER_SENSOR * 2], d, dd; + int fifo_count, packet_count, ind, s; + + packet_size = BYTES_PER_SENSOR * 2; + + /* disable interrupt */ + result = inv_plat_single_write(st, REG_INT_ENABLE, 0); + if (result) + return result; + /* disable the sensor output to FIFO */ + result = inv_plat_single_write(st, REG_FIFO_EN, 0); + if (result) + return result; + /* disable fifo reading */ + result = inv_plat_single_write(st, REG_USER_CTRL, 0); + if (result) + return result; + /* clear FIFO */ + result = inv_plat_single_write(st, REG_USER_CTRL, BIT_FIFO_RST); + if (result) + return result; + /* setup parameters */ + result = inv_plat_single_write(st, REG_CONFIG, INV_FILTER_98HZ); + if (result) + return result; + + /* gyro lp mode */ + if (lp_mode == 1) + d = BIT_GYRO_CYCLE_EN; + else if (lp_mode == 2) + d = DEF_SELFTEST_SAMPLE_RATE_ACC_LP; + else + d = 0; + result = inv_plat_single_write(st, REG_LP_MODE_CTRL, d); + if (result) + return result; + + /* config accel LPF register */ + if (lp_mode == 2) + d = BIT_ACCEL_FCHOCIE_B; + else + d = DEF_ST_MPU6500_ACCEL_LPF; + result = inv_plat_single_write(st, REG_6500_ACCEL_CONFIG2, d); + if (result) + return result; + + if (lp_mode) { + result = inv_plat_single_write(st, REG_SAMPLE_RATE_DIV, + DEF_SELFTEST_SAMPLE_RATE_LP); + } else { + result = inv_plat_single_write(st, REG_SAMPLE_RATE_DIV, + DEF_SELFTEST_SAMPLE_RATE); + } + if (result) + return result; + /* wait for the sampling rate change to stabilize */ + mdelay(INV_MPU_SAMPLE_RATE_CHANGE_STABLE); + result = inv_plat_single_write(st, REG_GYRO_CONFIG, + self_test_flag | DEF_SELFTEST_GYRO_FS); + if (result) + return result; + + d = DEF_SELFTEST_6500_ACCEL_FS; + d |= self_test_flag; + result = inv_plat_single_write(st, REG_ACCEL_CONFIG, d); + if (result) + return result; + + /* wait for the output to get stable */ + msleep(DEF_ST_6500_STABLE_TIME); + + /* enable FIFO reading */ + result = inv_plat_single_write(st, REG_USER_CTRL, BIT_FIFO_EN); + if (result) + return result; + /* enable sensor output to FIFO */ + d = BITS_GYRO_OUT | BIT_ACCEL_OUT; + for (i = 0; i < THREE_AXIS; i++) { + gyro_result[i] = 0; + accel_result[i] = 0; + } + s = 0; + while (s < 200 /*st->self_test.samples*/) { + /* Stop FIFO */ + result = inv_plat_single_write(st, REG_USER_CTRL, 0); + if (result) + return result; + /* clear FIFO */ + result = inv_plat_single_write(st, REG_USER_CTRL, BIT_FIFO_RST); + if (result) + return result; + /* enable FIFO reading */ + result = inv_plat_single_write(st, REG_USER_CTRL, BIT_FIFO_EN); + if (result) + return result; + + /* accel lp mode */ + dd = BIT_CLK_PLL; + if (lp_mode == 2) + dd |= BIT_LP_EN; + else + dd &= ~BIT_LP_EN; + result = inv_plat_single_write(st, REG_PWR_MGMT_1, dd); + if (result) + return result; + + result = inv_plat_single_write(st, REG_FIFO_EN, d); + if (result) + return result; + if (lp_mode) + mdelay(DEF_GYRO_WAIT_TIME_LP); + else + mdelay(DEF_GYRO_WAIT_TIME); + + result = inv_plat_single_write(st, REG_FIFO_EN, 0); + if (result) + return result; + + result = inv_plat_read(st, REG_FIFO_COUNT_H, + FIFO_COUNT_BYTE, data); + if (result) + return result; + fifo_count = be16_to_cpup((__be16 *)(&data[0])); + pr_debug("%s self_test fifo_count - %d\n", + st->hw->name, fifo_count); + packet_count = fifo_count / packet_size; + i = 0; + while ((i < packet_count) && (s < 200 /*st->self_test.samples*/)) { + short vals[3]; + result = inv_plat_read(st, REG_FIFO_R_W, + packet_size, data); + if (result) + return result; + ind = 0; + + for (j = 0; j < THREE_AXIS; j++) { + vals[j] = (short)be16_to_cpup( + (__be16 *)(&data[ind + 2 * j])); + accel_result[j] += vals[j]; + } + ind += BYTES_PER_SENSOR; + pr_debug( + "%s self_test accel data - %d %+d %+d %+d", + st->hw->name, s, vals[0], vals[1], vals[2]); + + for (j = 0; j < THREE_AXIS; j++) { + vals[j] = (short)be16_to_cpup( + (__be16 *)(&data[ind + 2 * j])); + gyro_result[j] += vals[j]; + } + pr_debug("%s self_test gyro data - %d %+d %+d %+d", + st->hw->name, s, vals[0], vals[1], vals[2]); + + s++; + i++; + } + } + + for (j = 0; j < THREE_AXIS; j++) { + accel_result[j] = accel_result[j] / s; + accel_result[j] *= DEF_ST_PRECISION; + } + for (j = 0; j < THREE_AXIS; j++) { + gyro_result[j] = gyro_result[j] / s; + gyro_result[j] *= DEF_ST_PRECISION; + } + + return 0; +} + + +int inv_power_up_self_test(struct inv_mpu_state *st) +{ + int result; + + result = inv_switch_power_in_lp(st, true); + + /* make sure no interrupts */ + result = inv_plat_single_write(st, REG_INT_ENABLE, 0); + if (result) + return result; + + if (result) + return result; + result = inv_switch_engine(st, true, BIT_PWR_ACCEL_STBY); + if (result) + return result; + result = inv_switch_engine(st, true, BIT_PWR_GYRO_STBY); + if (result) + return result; + + return 0; +} + +/* + * inv_hw_self_test() - main function to do hardware self test + */ +int inv_hw_self_test(struct inv_mpu_state *st) +{ + int result; + int gyro_bias_st[THREE_AXIS], gyro_bias_regular[THREE_AXIS]; + int accel_bias_st[THREE_AXIS], accel_bias_regular[THREE_AXIS]; +#if 0 + int gyro_bias_regular_lp[THREE_AXIS]; + int accel_bias_regular_lp[THREE_AXIS]; + int dummy_bias_regular[THREE_AXIS]; +#endif + int test_times, i; + char accel_result, gyro_result; + + result = inv_save_setting(st); + if (result) + return result; + + result = inv_power_up_self_test(st); + if (result) + return result; + accel_result = 0; + gyro_result = 0; + test_times = DEF_ST_TRY_TIMES; + while (test_times > 0) { + result = inv_do_test(st, 0, gyro_bias_regular, + accel_bias_regular, 0); + if (result == -EAGAIN) + test_times--; + else + test_times = 0; + } + if (result) + goto test_fail; + pr_debug("%s self_test accel bias_regular - %+d %+d %+d\n", + st->hw->name, accel_bias_regular[0], + accel_bias_regular[1], accel_bias_regular[2]); + pr_debug("%s self_test gyro bias_regular - %+d %+d %+d\n", + st->hw->name, gyro_bias_regular[0], gyro_bias_regular[1], + gyro_bias_regular[2]); + + test_times = DEF_ST_TRY_TIMES; + while (test_times > 0) { + result = inv_do_test(st, BITS_SELF_TEST_EN, gyro_bias_st, + accel_bias_st, 0); + if (result == -EAGAIN) + test_times--; + else + break; + } + if (result) + goto test_fail; + pr_debug("%s self_test accel bias_st - %+d %+d %+d\n", + st->hw->name, accel_bias_st[0], accel_bias_st[1], + accel_bias_st[2]); + pr_debug("%s self_test gyro bias_st - %+d %+d %+d\n", + st->hw->name, gyro_bias_st[0], gyro_bias_st[1], + gyro_bias_st[2]); + +#if 0 + /* lp gyro mode */ + test_times = DEF_ST_TRY_TIMES; + while (test_times > 0) { + result = inv_do_test(st, 0, gyro_bias_regular_lp, + dummy_bias_regular, 1); + if (result == -EAGAIN) + test_times--; + else + test_times = 0; + } + if (result) + goto test_fail; + pr_debug("%s self_test gyro bias_regular lp - %+d %+d %+d\n", + st->hw->name, gyro_bias_regular_lp[0], gyro_bias_regular_lp[1], + gyro_bias_regular_lp[2]); + + /* lp accel mode */ + test_times = DEF_ST_TRY_TIMES; + while (test_times > 0) { + result = inv_do_test(st, 0, dummy_bias_regular, + accel_bias_regular_lp, 2); + if (result == -EAGAIN) + test_times--; + else + test_times = 0; + } + if (result) + goto test_fail; + pr_debug("%s self_test accel bias_regular lp - %+d %+d %+d\n", + st->hw->name, accel_bias_regular_lp[0], + accel_bias_regular_lp[1], accel_bias_regular_lp[2]); +#endif + + /* copy bias */ + for (i = 0; i < 3; i++) { + /* gyro : LN bias as LN is default mode */ + st->gyro_st_bias[i] = gyro_bias_regular[i] / DEF_ST_PRECISION; + /* accel : LN bias as LN is default mode */ + st->accel_st_bias[i] = accel_bias_regular[i] / DEF_ST_PRECISION; + } + + /* Check is done on continuous mode data */ + accel_result = !inv_check_accel_self_test(st, + accel_bias_regular, accel_bias_st); + gyro_result = !inv_check_gyro_self_test(st, + gyro_bias_regular, gyro_bias_st); + +test_fail: + inv_recover_setting(st); + return (accel_result << DEF_ST_ACCEL_RESULT_SHIFT) | gyro_result; +} diff --git a/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_setup_20680.c b/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_setup_20680.c new file mode 100644 index 000000000000..5e9cf8906d79 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/iam20680/inv_mpu_setup_20680.c @@ -0,0 +1,466 @@ +/* +* Copyright (C) 2017-2018 InvenSense, Inc. +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#define pr_fmt(fmt) "inv_mpu: " fmt +#include "../inv_mpu_iio.h" + +/* set LN mode for gyro regardless of conditions */ +#define USE_GYRO_LN_MODE + +static int inv_calc_engine_dur(struct inv_engine_info *ei) +{ + if (!ei->running_rate) + return -EINVAL; + ei->dur = ei->base_time / ei->orig_rate; + ei->dur *= ei->divider; + + return 0; +} + +static int inv_turn_on_fifo(struct inv_mpu_state *st) +{ + u8 int_en, fifo_en, mode, user; + int r; + + r = inv_plat_single_write(st, REG_FIFO_EN, 0); + if (r) + return r; + r = inv_plat_single_write(st, REG_USER_CTRL, BIT_FIFO_RST); + if (r) + return r; + fifo_en = 0; + int_en = 0; + + if (st->gesture_only_on && (!st->batch.timeout)) { + st->gesture_int_count = WOM_DELAY_THRESHOLD; + int_en |= BIT_WOM_ALL_INT_EN; + } +#ifdef TIMER_BASED_BATCHING + if (st->chip_config.eis_enable) + int_en |= BIT_FSYNC_INT_EN; + if (!st->batch_timeout) { + int_en |= BIT_DATA_RDY_EN; + } +#else + if (st->batch.timeout) { + if(!st->batch.fifo_wm_th) + int_en = BIT_DATA_RDY_EN; + } else { + int_en = BIT_DATA_RDY_EN; + if (st->chip_config.eis_enable) + int_en |= BIT_FSYNC_INT_EN; + } +#endif + if (st->sensor[SENSOR_GYRO].on) + fifo_en |= BITS_GYRO_FIFO_EN; + + if (st->sensor[SENSOR_ACCEL].on) + fifo_en |= BIT_ACCEL_FIFO_EN; + r = inv_plat_single_write(st, REG_FIFO_EN, fifo_en); + if (r) + return r; + st->int_en = int_en; + r = inv_plat_single_write(st, REG_INT_ENABLE, int_en); + if (r) + return r; + if (st->gesture_only_on && (!st->batch.timeout)) { + mode = BIT_ACCEL_INTEL_EN | BIT_ACCEL_INTEL_MODE; + } else { + mode = 0; + } + r = inv_plat_single_write(st, REG_ACCEL_INTEL_CTRL, mode); +#ifdef SENSOR_DATA_FROM_REGISTERS + user = 0; +#else + user = BIT_FIFO_EN; +#endif + r = inv_plat_single_write(st, REG_USER_CTRL, user | st->i2c_dis); +#ifdef TIMER_BASED_BATCHING + if (fifo_en && st->batch_timeout) { + if (st->is_batch_timer_running) + hrtimer_cancel(&st ->hr_batch_timer); + st->is_batch_timer_running = true; + hrtimer_start(&st ->hr_batch_timer, + ns_to_ktime(st->batch_timeout), HRTIMER_MODE_REL); + } else { + if (st->is_batch_timer_running) + hrtimer_cancel(&st ->hr_batch_timer); + st->is_batch_timer_running = false; + } +#endif + + return r; +} + +/* + * inv_reset_fifo() - Reset FIFO related registers. + */ +int inv_reset_fifo(struct inv_mpu_state *st, bool turn_off) +{ + int r, i; + struct inv_timestamp_algo *ts_algo = &st->ts_algo; + int dur_ms; + + r = inv_turn_on_fifo(st); + if (r) + return r; + + ts_algo->last_run_time = get_time_ns(); + ts_algo->reset_ts = ts_algo->last_run_time; + if (st->mode_1k_on) + ts_algo->first_sample = MODE_1K_INIT_SAMPLE; + else + ts_algo->first_sample = 1; + + dur_ms = st->smplrt_div + 1; + if ((ts_algo->first_sample * dur_ms) < FIRST_SAMPLE_BUF_MS) + ts_algo->first_sample = FIRST_SAMPLE_BUF_MS / dur_ms; + if (ts_algo->first_sample == 0) + ts_algo->first_sample = 1; + + st->last_temp_comp_time = ts_algo->last_run_time; + st->left_over_size = 0; + for (i = 0; i < SENSOR_NUM_MAX; i++) { + st->sensor[i].calib_flag = 0; + st->sensor[i].sample_calib = 0; + st->sensor[i].time_calib = ts_algo->last_run_time; + } + + ts_algo->calib_counter = 0; + + return 0; +} + +static int inv_turn_on_engine(struct inv_mpu_state *st) +{ + u8 v, w; + int r; + unsigned int wait_ms; + + if (st->chip_config.gyro_enable | st->chip_config.accel_enable) { + w = 0; + if (!st->chip_config.gyro_enable) + w |= BIT_PWR_GYRO_STBY; + if (!st->chip_config.accel_enable) + w |= BIT_PWR_ACCEL_STBY; + } else if (st->chip_config.compass_enable) { + w = BIT_PWR_GYRO_STBY; + } else { + w = (BIT_PWR_GYRO_STBY | BIT_PWR_ACCEL_STBY); + } + + r = inv_plat_read(st, REG_PWR_MGMT_2, 1, &v); + if (r) + return r; + r = inv_plat_single_write(st, REG_PWR_MGMT_2, w); + if (r) + return r; + + wait_ms = 0; + if (st->chip_config.gyro_enable + && (v & BIT_PWR_GYRO_STBY)) { + wait_ms = INV_IAM20680_GYRO_START_TIME; + } + if (st->chip_config.accel_enable + && (v & BIT_PWR_ACCEL_STBY)) { + if (INV_IAM20680_ACCEL_START_TIME > wait_ms) + wait_ms = INV_IAM20680_ACCEL_START_TIME; + } + if (wait_ms) + msleep(wait_ms); + + if (st->chip_config.has_compass) { + if (st->chip_config.compass_enable) + r = st->slave_compass->resume(st); + else + r = st->slave_compass->suspend(st); + if (r) + return r; + } + + return 0; +} + +static int inv_setup_dmp_rate(struct inv_mpu_state *st) +{ + int i; + + for (i = 0; i < SENSOR_NUM_MAX; i++) { + if (st->sensor[i].on) { + st->cntl |= st->sensor[i].output; + st->sensor[i].dur = + st->eng_info[st->sensor[i].engine_base].dur; + st->sensor[i].div = 1; + } + } + + return 0; +} + +/* + * inv_set_lpf() - set low pass filer based on fifo rate. + */ +static int inv_set_lpf(struct inv_mpu_state *st, int rate) +{ + const short hz[] = {188, 98, 42, 20, 10, 5}; + const int d[] = {INV_FILTER_188HZ, INV_FILTER_98HZ, + INV_FILTER_42HZ, INV_FILTER_20HZ, + INV_FILTER_10HZ, INV_FILTER_5HZ}; + int i, h, data, result; + +#ifdef USE_GYRO_LN_MODE + if (1) { +#else + if (st->chip_config.eis_enable || st->ois.en || st->mode_1k_on) { +#endif + h = (rate >> 1); + i = 0; + while ((h < hz[i]) && (i < ARRAY_SIZE(d) - 1)) + i++; + data = d[i]; + data |= EXT_SYNC_SET; + result = inv_plat_single_write(st, REG_CONFIG, data); + if (result) + return result; + + st->chip_config.lpf = data; + result = inv_plat_single_write(st, REG_LP_MODE_CTRL, 0); + } else { + result = inv_plat_single_write(st, REG_LP_MODE_CTRL, + BIT_GYRO_CYCLE_EN); + if (result) + return result; + data = 0; + result = inv_plat_single_write(st, REG_CONFIG, data | 3); + } + + return result; +} + +static int inv_set_div(struct inv_mpu_state *st, int a_d, int g_d) +{ + int result, div; + + if (st->chip_config.gyro_enable) + div = g_d; + else + div = a_d; + if (st->chip_config.eis_enable) + div = 0; + + st->smplrt_div = div; + pr_debug("div= %d\n", div); + result = inv_plat_single_write(st, REG_SAMPLE_RATE_DIV, div); + + return result; +} + +// 20680 does not support batching +static int inv_set_batch(struct inv_mpu_state *st) +{ +#ifdef TIMER_BASED_BATCHING + u64 timeout; + int required_fifo_size; + + if (st->batch.timeout) { + required_fifo_size = st->batch.timeout * st->eng_info[ENGINE_GYRO].running_rate + * st->batch.pk_size / 1000; + if (required_fifo_size > MAX_BATCH_FIFO_SIZE) { + required_fifo_size = MAX_BATCH_FIFO_SIZE; + timeout = (required_fifo_size / st->batch.pk_size) * (1000 / st->eng_info[ENGINE_GYRO].running_rate); + } else { + timeout = st->batch.timeout; + } + } else { + timeout = 1000 / st->eng_info[ENGINE_GYRO].running_rate; + } + if (timeout <= 1000 / st->eng_info[ENGINE_GYRO].running_rate) + st->batch_timeout = 0; + else + st->batch_timeout = timeout * 1000000; // ms to ns +#endif + st->batch.fifo_wm_th = 0; + + return 0; +} + +static int inv_set_rate(struct inv_mpu_state *st) +{ + int g_d, a_d, result, i; + + result = inv_setup_dmp_rate(st); + if (result) + return result; + + g_d = st->eng_info[ENGINE_GYRO].divider - 1; + a_d = st->eng_info[ENGINE_ACCEL].divider - 1; + result = inv_set_div(st, a_d, g_d); + if (result) + return result; + result = inv_set_lpf(st, st->eng_info[ENGINE_GYRO].running_rate); + if (result) + return result; + // set ADLPF at this point not to change after accel is enabled + result = inv_set_accel_config2(st, false); + st->batch.pk_size = 0; + for (i = 0; i < SENSOR_NUM_MAX; i++) { + if (st->sensor[i].on) + st->batch.pk_size += st->sensor[i].sample_size; + } + + inv_set_batch(st); + + return result; +} + +static int inv_determine_engine(struct inv_mpu_state *st) +{ + int i; + bool a_en, g_en; + int accel_rate, gyro_rate; + + a_en = false; + g_en = false; + gyro_rate = MPU_INIT_SENSOR_RATE; + accel_rate = MPU_INIT_SENSOR_RATE; + /* loop the streaming sensors to see which engine needs to be turned on + */ + for (i = 0; i < SENSOR_NUM_MAX; i++) { + if (st->sensor[i].on) { + a_en |= st->sensor[i].a_en; + g_en |= st->sensor[i].g_en; + } + } + + if (st->chip_config.eis_enable) { + g_en = true; + st->eis.frame_count = 0; + st->eis.fsync_delay = 0; + st->eis.gyro_counter = 0; + st->eis.voting_count = 0; + st->eis.voting_count_sub = 0; + gyro_rate = BASE_SAMPLE_RATE; + } else { + st->eis.eis_triggered = false; + st->eis.prev_state = false; + } + + accel_rate = st->sensor[SENSOR_ACCEL].rate; + gyro_rate = max(gyro_rate, st->sensor[SENSOR_GYRO].rate); + + st->ts_algo.clock_base = ENGINE_ACCEL; + + if (g_en) { + /* gyro engine needs to be fastest */ + if (a_en) + gyro_rate = max(gyro_rate, accel_rate); + accel_rate = gyro_rate; + st->ts_algo.clock_base = ENGINE_GYRO; + } else if (a_en) { + /* accel engine needs to be fastest if gyro engine is off */ + gyro_rate = accel_rate; + st->ts_algo.clock_base = ENGINE_ACCEL; + } + + st->eng_info[ENGINE_GYRO].running_rate = gyro_rate; + st->eng_info[ENGINE_ACCEL].running_rate = accel_rate; + if ((gyro_rate >= BASE_SAMPLE_RATE) || + (accel_rate >= BASE_SAMPLE_RATE)) + st->mode_1k_on = true; + else + st->mode_1k_on = false; + /* engine divider for pressure and compass is set later */ + if (st->chip_config.eis_enable || st->mode_1k_on) { + st->eng_info[ENGINE_GYRO].divider = 1; + st->eng_info[ENGINE_ACCEL].divider = 1; + // need to update rate and div for 1khz mode + for ( i = 0 ; i < SENSOR_L_NUM_MAX ; i++ ) { + if (st->sensor_l[i].on) { + st->sensor_l[i].counter = 0; + if (st->sensor_l[i].rate) + st->sensor_l[i].div = + BASE_SAMPLE_RATE + / st->sensor_l[i].rate; + else + st->sensor_l[i].div = 0xffff; + } + } + } else { + st->eng_info[ENGINE_GYRO].divider = BASE_SAMPLE_RATE / + st->eng_info[ENGINE_GYRO].running_rate; + st->eng_info[ENGINE_ACCEL].divider = BASE_SAMPLE_RATE / + st->eng_info[ENGINE_ACCEL].running_rate; + } + + for ( i = 0 ; i < SENSOR_L_NUM_MAX ; i++ ) + st->sensor_l[i].counter = 0; + + inv_calc_engine_dur(&st->eng_info[ENGINE_GYRO]); + inv_calc_engine_dur(&st->eng_info[ENGINE_ACCEL]); + + pr_debug("gen: %d aen: %d grate: %d arate: %d\n", + g_en, a_en, gyro_rate, accel_rate); + + st->chip_config.gyro_enable = g_en; + st->chip_config.accel_enable = a_en; + + return 0; +} + +/* + * set_inv_enable() - enable function. + */ +int set_inv_enable(struct iio_dev *indio_dev) +{ + int result; + struct inv_mpu_state *st = iio_priv(indio_dev); + + result = inv_switch_power_in_lp(st, true); + if (result) + return result; + inv_stop_interrupt(st); + inv_determine_engine(st); + result = inv_set_rate(st); + if (result) { + pr_err("inv_set_rate error\n"); + return result; + } + result = inv_turn_on_engine(st); + if (result) { + pr_err("inv_turn_on_engine error\n"); + return result; + } + result = inv_reset_fifo(st, false); + if (result) + return result; + result = inv_switch_power_in_lp(st, false); + if ((!st->chip_config.gyro_enable) && + (!st->chip_config.accel_enable)) { + inv_set_power(st, false); + return 0; + } + + return result; +} +/* dummy function for 20608D */ +int inv_enable_pedometer_interrupt(struct inv_mpu_state *st, bool en) +{ + return 0; +} +int inv_dmp_read(struct inv_mpu_state *st, int off, int size, u8 *buf) +{ + return 0; +} +int inv_firmware_load(struct inv_mpu_state *st) +{ + return 0; +} diff --git a/drivers/iio/imu/inv_mpu/inv_mpu_common.c b/drivers/iio/imu/inv_mpu/inv_mpu_common.c new file mode 100644 index 000000000000..33db03418b92 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_mpu_common.c @@ -0,0 +1,988 @@ +/* + * Copyright (C) 2012-2017 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#define pr_fmt(fmt) "inv_mpu: " fmt +#include "inv_mpu_iio.h" +#ifdef CONFIG_RTC_INTF_ALARM +#include <linux/android_alarm.h> +#endif +#include <linux/export.h> + +#ifdef CONFIG_RTC_INTF_ALARM +s64 get_time_ns(void) +{ + struct timespec ts; + + /* get_monotonic_boottime(&ts); */ + + /* Workaround for some platform on which monotonic clock and + * Android SystemClock has a gap. + * Use ktime_to_timespec(alarm_get_elapsed_realtime()) instead of + * get_monotonic_boottime() for these platform + */ + + ts = ktime_to_timespec(alarm_get_elapsed_realtime()); + + return timespec_to_ns(&ts); +} +#else +s64 get_time_ns(void) +{ + struct timespec ts; + + get_monotonic_boottime(&ts); + + /* Workaround for some platform on which monotonic clock and + * Android SystemClock has a gap. + * Use ktime_to_timespec(alarm_get_elapsed_realtime()) instead of + * get_monotonic_boottime() for these platform + */ + return timespec_to_ns(&ts); +} + +#endif + +#ifdef ACCEL_BIAS_TEST +int inv_get_3axis_average(s16 src[], s16 dst[], s16 reset) +{ +#define BUFFER_SIZE 200 + static s16 buffer[BUFFER_SIZE][3]; + static s16 current_position = 0; + static s16 ready = 0; + int sum[3]= {0,}; + int i; + + if(reset){ + current_position = 0; + ready = 0; + } + buffer[current_position][0] = src[0]; + buffer[current_position][1] = src[1]; + buffer[current_position][2] = src[2]; + current_position++; + if(current_position == BUFFER_SIZE){ + ready = 1; + current_position = 0; + } + if(ready){ + for(i = 0 ; i < BUFFER_SIZE ; i++){ + sum[0] += buffer[i][0]; + sum[1] += buffer[i][1]; + sum[2] += buffer[i][2]; + } + dst[0] = sum[0]/BUFFER_SIZE; + dst[1] = sum[1]/BUFFER_SIZE; + dst[2] = sum[2]/BUFFER_SIZE; + return 1; + } + return 0; +} +#endif + +int inv_q30_mult(int a, int b) +{ +#define DMP_MULTI_SHIFT 30 + u64 temp; + int result; + + temp = ((u64)a) * b; + result = (int)(temp >> DMP_MULTI_SHIFT); + + return result; +} +#if defined(CONFIG_INV_MPU_IIO_ICM20648) || \ + defined(CONFIG_INV_MPU_IIO_ICM20690) +/* inv_read_secondary(): set secondary registers for reading. + The chip must be set as bank 3 before calling. + */ +int inv_read_secondary(struct inv_mpu_state *st, int ind, int addr, + int reg, int len) +{ + int result; + + result = inv_plat_single_write(st, st->slv_reg[ind].addr, + INV_MPU_BIT_I2C_READ | addr); + if (result) + return result; + result = inv_plat_single_write(st, st->slv_reg[ind].reg, reg); + if (result) + return result; + result = inv_plat_single_write(st, st->slv_reg[ind].ctrl, + INV_MPU_BIT_SLV_EN | len); + + return result; +} + +int inv_execute_read_secondary(struct inv_mpu_state *st, int ind, int addr, + int reg, int len, u8 *d) +{ + int result; + + inv_set_bank(st, BANK_SEL_3); + result = inv_read_secondary(st, ind, addr, reg, len); + if (result) + return result; + inv_set_bank(st, BANK_SEL_0); + result = inv_plat_single_write(st, REG_USER_CTRL, st->i2c_dis | + BIT_I2C_MST_EN); + msleep(SECONDARY_INIT_WAIT); + result = inv_plat_single_write(st, REG_USER_CTRL, st->i2c_dis); + if (result) + return result; + result = inv_plat_read(st, REG_EXT_SLV_SENS_DATA_00, len, d); + + return result; +} + +/* inv_write_secondary(): set secondary registers for writing. + The chip must be set as bank 3 before calling. + */ +int inv_write_secondary(struct inv_mpu_state *st, int ind, int addr, + int reg, int v) +{ + int result; + + result = inv_plat_single_write(st, st->slv_reg[ind].addr, addr); + if (result) + return result; + result = inv_plat_single_write(st, st->slv_reg[ind].reg, reg); + if (result) + return result; + result = inv_plat_single_write(st, st->slv_reg[ind].ctrl, + INV_MPU_BIT_SLV_EN | 1); + + result = inv_plat_single_write(st, st->slv_reg[ind].d0, v); + + return result; +} + +int inv_execute_write_secondary(struct inv_mpu_state *st, int ind, int addr, + int reg, int v) +{ + int result; + + inv_set_bank(st, BANK_SEL_3); + result = inv_write_secondary(st, ind, addr, reg, v); + if (result) + return result; + inv_set_bank(st, BANK_SEL_0); + result = inv_plat_single_write(st, REG_USER_CTRL, st->i2c_dis | + BIT_I2C_MST_EN); + msleep(SECONDARY_INIT_WAIT); + result = inv_plat_single_write(st, REG_USER_CTRL, st->i2c_dis); + + return result; +} + +int inv_set_bank(struct inv_mpu_state *st, u8 bank) +{ +#ifdef CONFIG_INV_MPU_IIO_ICM20648 + int r; + + r = inv_plat_single_write(st, REG_BANK_SEL, bank); + + return r; +#else + return 0; +#endif +} +#endif + +#ifdef CONFIG_INV_MPU_IIO_ICM20648 +/** + * inv_write_cntl() - Write control word to designated address. + * @st: Device driver instance. + * @wd: control word. + * @en: enable/disable. + * @cntl: control address to be written. + */ +int inv_write_cntl(struct inv_mpu_state *st, u16 wd, bool en, int cntl) +{ + int result; + u8 reg[2], d_out[2]; + + result = mem_r(cntl, 2, d_out); + if (result) + return result; + reg[0] = ((wd >> 8) & 0xff); + reg[1] = (wd & 0xff); + if (!en) { + d_out[0] &= ~reg[0]; + d_out[1] &= ~reg[1]; + } else { + d_out[0] |= reg[0]; + d_out[1] |= reg[1]; + } + result = mem_w(cntl, 2, d_out); + + return result; +} +#endif + +int inv_set_power(struct inv_mpu_state *st, bool power_on) +{ + u8 d; + int r; + + if ((!power_on) == st->chip_config.is_asleep) + return 0; + + d = BIT_CLK_PLL; + if (!power_on) + d |= BIT_SLEEP; + + r = inv_plat_single_write(st, REG_PWR_MGMT_1, d); + if (r) + return r; + + if (power_on) + usleep_range(REG_UP_TIME_USEC, REG_UP_TIME_USEC); + + st->chip_config.is_asleep = !power_on; + + return 0; +} +EXPORT_SYMBOL_GPL(inv_set_power); + +int inv_stop_interrupt(struct inv_mpu_state *st) +{ + int res; +#if defined(CONFIG_INV_MPU_IIO_ICM20648) + /* disable_irq_wake alone should work already. However, + it might need system configuration change. From driver side, + we will disable IRQ altogether for non-wakeup sensors. */ + res = inv_plat_read(st, REG_INT_ENABLE, 1, &st->int_en); + if (res) + return res; + res = inv_plat_read(st, REG_INT_ENABLE_2, 1, &st->int_en_2); + if (res) + return res; + res = inv_plat_single_write(st, REG_INT_ENABLE, 0); + if (res) + return res; + res = inv_plat_single_write(st, REG_INT_ENABLE_2, 0); + if (res) + return res; +#endif +#if defined(CONFIG_INV_MPU_IIO_ICM20608D) + res = inv_plat_read(st, REG_INT_ENABLE, 1, &st->int_en); + if (res) + return res; + res = inv_plat_single_write(st, REG_INT_ENABLE, 0); + if (res) + return res; +#endif +#if defined(CONFIG_INV_MPU_IIO_ICM20602) \ + || defined(CONFIG_INV_MPU_IIO_ICM20690) \ + || defined(CONFIG_INV_MPU_IIO_IAM20680) + res = inv_plat_read(st, REG_INT_ENABLE, 1, &st->int_en); + if (res) + return res; + res = inv_plat_single_write(st, REG_INT_ENABLE, 0); + if (res) + return res; +#endif + return 0; +} +int inv_reenable_interrupt(struct inv_mpu_state *st) +{ + int res = 0; +#if defined(CONFIG_INV_MPU_IIO_ICM20648) + res = inv_plat_single_write(st, REG_INT_ENABLE, st->int_en); + if (res) + return res; + res = inv_plat_single_write(st, REG_INT_ENABLE_2, st->int_en_2); + if (res) + return res; +#elif defined(CONFIG_INV_MPU_IIO_ICM20608D) + res = inv_plat_single_write(st, REG_INT_ENABLE, st->int_en); + if (res) + return res; +#endif +#if defined(CONFIG_INV_MPU_IIO_ICM20602) \ + || defined(CONFIG_INV_MPU_IIO_ICM20690) \ + || defined(CONFIG_INV_MPU_IIO_IAM20680) + res = inv_plat_single_write(st, REG_INT_ENABLE, st->int_en); + if (res) + return res; +#endif + return res; +} + +static int inv_lp_en_off_mode(struct inv_mpu_state *st, bool on) +{ + int r; + + if (!st->chip_config.is_asleep) + return 0; + + r = inv_plat_single_write(st, REG_PWR_MGMT_1, BIT_CLK_PLL); + st->chip_config.is_asleep = 0; + + return r; +} +#ifdef CONFIG_INV_MPU_IIO_ICM20648 +static int inv_lp_en_on_mode(struct inv_mpu_state *st, bool on) +{ + int r = 0; + u8 w; + + if ((!st->chip_config.is_asleep) && + ((!on) == st->chip_config.lp_en_set)) + return 0; + + w = BIT_CLK_PLL; + if ((!on) && (!st->eis.eis_triggered)) + w |= BIT_LP_EN; + r = inv_plat_single_write(st, REG_PWR_MGMT_1, w); + st->chip_config.is_asleep = 0; + st->chip_config.lp_en_set = (!on); + return r; +} +#endif +#if defined(CONFIG_INV_MPU_IIO_ICM20602) \ + || defined(CONFIG_INV_MPU_IIO_ICM20690) \ + || defined(CONFIG_INV_MPU_IIO_IAM20680) +int inv_set_accel_config2(struct inv_mpu_state *st, bool cycle_mode) +{ + int cycle_freq[] = {275, 192, 111, 59}; + int cont_freq[] = {219, 219, 99, 45, 22, 11, 6}; + int i, r, rate; + u8 v; + + v = 0; +#ifdef CONFIG_INV_MPU_IIO_ICM20690 + v |= BIT_FIFO_SIZE_1K; +#endif + if (cycle_mode) { + rate = (st->eng_info[ENGINE_ACCEL].running_rate << 1); + i = ARRAY_SIZE(cycle_freq) - 1; + while (i > 0) { + if (rate < cycle_freq[i]) { + break; + } + i--; + } + r = inv_plat_single_write(st, REG_ACCEL_CONFIG_2, v | + (i << 4) | 7); + if (r) + return r; + } else { + rate = (st->eng_info[ENGINE_ACCEL].running_rate >> 1); + for (i = 1; i < ARRAY_SIZE(cont_freq); i++) { + if (rate >= cont_freq[i]) + break; + } + if (i > 6) + i = 6; + r = inv_plat_single_write(st, REG_ACCEL_CONFIG_2, v | i); + if (r) + return r; + } + + return 0; +} +static int inv_lp_en_on_mode(struct inv_mpu_state *st, bool on) +{ + int r = 0; + u8 w; + bool cond_check; + + if ((!st->chip_config.is_asleep) && + ((!on) == st->chip_config.lp_en_set)) + return 0; + cond_check = (!on) && st->cycle_on; + + w = BIT_CLK_PLL; + r = inv_plat_single_write(st, REG_PWR_MGMT_1, w); + if (cond_check) { + w |= BIT_LP_EN; + inv_set_accel_config2(st, true); + st->chip_config.lp_en_set = true; + r = inv_plat_single_write(st, REG_PWR_MGMT_1, w); + } else { + inv_set_accel_config2(st, false); +#ifdef CONFIG_INV_MPU_IIO_ICM20690 + r = inv_plat_single_write(st, REG_PWR_MGMT_1, w | BIT_SLEEP); + if (r) + return r; +#endif + st->chip_config.lp_en_set = false; + r = inv_plat_single_write(st, REG_PWR_MGMT_1, w); + msleep(10); + } + st->chip_config.is_asleep = 0; + + return r; +} +#endif +#ifdef CONFIG_INV_MPU_IIO_ICM20608D +static int inv_set_accel_config2(struct inv_mpu_state *st) +{ + int cont_freq[] = {219, 219, 99, 45, 22, 11, 6}; + int dec2_cfg = 0; + int i, r, rate; + + rate = (st->eng_info[ENGINE_ACCEL].running_rate << 1); + i = 0; + if (!st->chip_config.eis_enable){ + while ((rate < cont_freq[i]) && (i < ARRAY_SIZE(cont_freq) - 1)) + i++; + dec2_cfg = 2<<4; //4x + } + r = inv_plat_single_write(st, REG_ACCEL_CONFIG_2, i | dec2_cfg); + if (r) + return r; + return 0; +} +static int inv_lp_en_on_mode(struct inv_mpu_state *st, bool on) +{ + int r = 0; + u8 w; + + w = BIT_CLK_PLL; + if ((!on) && (!st->chip_config.eis_enable)) + w |= BIT_LP_EN; + inv_set_accel_config2(st); + r = inv_plat_single_write(st, REG_PWR_MGMT_1, w); + + return r; +} +#endif +int inv_switch_power_in_lp(struct inv_mpu_state *st, bool on) +{ + int r; + + if (st->chip_config.lp_en_mode_off) + r = inv_lp_en_off_mode(st, on); + else + r = inv_lp_en_on_mode(st, on); + + return r; +} +EXPORT_SYMBOL_GPL(inv_switch_power_in_lp); + +int write_be16_to_mem(struct inv_mpu_state *st, u16 data, int addr) +{ + u8 d[2]; + + d[0] = (data >> 8) & 0xff; + d[1] = data & 0xff; + + return mem_w(addr, sizeof(d), d); +} + +int write_be32_to_mem(struct inv_mpu_state *st, u32 data, int addr) +{ + cpu_to_be32s(&data); + return mem_w(addr, sizeof(data), (u8 *)&data); +} + +int read_be16_from_mem(struct inv_mpu_state *st, u16 *o, int addr) +{ + int result; + u8 d[2]; + + result = mem_r(addr, 2, (u8 *) &d); + *o = d[0] << 8 | d[1]; + + return result; +} + +int read_be32_from_mem(struct inv_mpu_state *st, u32 *o, int addr) +{ + int result; + u32 d = 0; + + result = mem_r(addr, 4, (u8 *) &d); + *o = be32_to_cpup((__be32 *)(&d)); + + return result; +} + +int be32_to_int(u8 *d) +{ + return (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3]; +} + +u32 inv_get_cntr_diff(u32 curr_counter, u32 prev) +{ + u32 diff; + + if (curr_counter > prev) + diff = curr_counter - prev; + else + diff = 0xffffffff - prev + curr_counter + 1; + + return diff; +} + +int inv_write_2bytes(struct inv_mpu_state *st, int addr, int data) +{ + u8 d[2]; + + if (data < 0 || data > USHRT_MAX) + return -EINVAL; + + d[0] = (u8) ((data >> 8) & 0xff); + d[1] = (u8) (data & 0xff); + + return mem_w(addr, ARRAY_SIZE(d), d); +} + + + +int inv_process_eis(struct inv_mpu_state *st, u16 delay) +{ + int tmp1, tmp2, tmp3; + + switch (st->eis.voting_state) { + case 0: + st->eis.gyro_counter_s[0] = st->eis.gyro_counter; + st->eis.fsync_delay_s[0] = delay - st->eis.fsync_delay; + st->eis.voting_count = 1; + st->eis.voting_count_sub = 0; + st->eis.voting_state = 1; + break; + case 1: + if (abs(st->eis.gyro_counter_s[0] - + st->eis.gyro_counter) <= 1) { + st->eis.voting_count++; + } else { + st->eis.gyro_counter_s[2] = st->eis.gyro_counter; + st->eis.voting_count_sub++; + st->eis.voting_state = 2; + } + if (st->eis.voting_count > 5) + st->eis.voting_state = 3; + break; + case 2: + tmp1 = abs(st->eis.gyro_counter_s[0] - st->eis.gyro_counter); + tmp2 = abs(st->eis.gyro_counter_s[2] - st->eis.gyro_counter); + + if ((tmp1 < tmp2) && (tmp1 <= 1)) + st->eis.voting_count++; + else + st->eis.voting_count_sub++; + if (st->eis.voting_count > 5) { + st->eis.voting_state = 3; + st->eis.voting_count = 0; + st->eis.voting_count_sub = 0; + } + + if (st->eis.voting_count_sub > 5) { + st->eis.gyro_counter_s[0] = st->eis.gyro_counter; + st->eis.fsync_delay_s[0] = delay - st->eis.fsync_delay; + st->eis.voting_state = 1; + st->eis.voting_count = 1; + st->eis.voting_count_sub = 0; + } + break; + case 3: + tmp1 = abs(st->eis.gyro_counter_s[0] - st->eis.gyro_counter); + if (tmp1 == 1) { + st->eis.gyro_counter_s[1] = st->eis.gyro_counter; + st->eis.fsync_delay_s[1] = delay - st->eis.fsync_delay; + st->eis.voting_state = 4; + st->eis.voting_count_sub = 1; + st->eis.voting_count = 1; + } + break; + case 4: + if (st->eis.gyro_counter == st->eis.gyro_counter_s[0]) { + tmp1 = delay - st->eis.fsync_delay; + tmp2 = abs(tmp1 - st->eis.fsync_delay_s[0]); + if (tmp2 < 3) { + st->eis.voting_count++; + } else { + st->eis.fsync_delay_s[2] = tmp1; + st->eis.voting_count_sub = 1; + st->eis.voting_state = 5; + } + if (st->eis.voting_count > 5) { + st->eis.voting_count = 1; + st->eis.voting_state = 6; + } + } + break; + case 5: + if (st->eis.gyro_counter == st->eis.gyro_counter_s[0]) { + tmp1 = delay - st->eis.fsync_delay; + + tmp2 = abs(tmp1 - st->eis.fsync_delay_s[0]); + tmp3 = abs(tmp1 - st->eis.fsync_delay_s[2]); + if ((tmp2 < tmp3) && (tmp2 < 3)) + st->eis.voting_count++; + else + st->eis.voting_count_sub++; + if ((st->eis.voting_count > 5) && + (st->eis.voting_count_sub + < st->eis.voting_count)) { + st->eis.voting_state = 6; + st->eis.voting_count = 1; + } else if (st->eis.voting_count_sub > 5) { + st->eis.fsync_delay_s[0] = tmp1; + st->eis.voting_state = 4; + st->eis.voting_count = 1; + } + + } + break; + case 6: + if (st->eis.gyro_counter == st->eis.gyro_counter_s[1]) { + tmp1 = delay - st->eis.fsync_delay; + tmp2 = abs(tmp1 - st->eis.fsync_delay_s[1]); + if (tmp2 < 3) { + st->eis.voting_count++; + } else { + st->eis.fsync_delay_s[2] = tmp1; + st->eis.voting_count_sub = 1; + st->eis.voting_count = 1; + st->eis.voting_state = 7; + } + if (st->eis.voting_count > 5) + st->eis.voting_state = 8; + } + break; + case 7: + if (st->eis.gyro_counter == st->eis.gyro_counter_s[1]) { + tmp1 = delay - st->eis.fsync_delay; + + tmp2 = abs(tmp1 - st->eis.fsync_delay_s[1]); + tmp3 = abs(tmp1 - st->eis.fsync_delay_s[2]); + if ((tmp2 < tmp3) && (tmp2 < 3)) + st->eis.voting_count++; + else + st->eis.voting_count_sub++; + if ((st->eis.voting_count > 5) && + (st->eis.voting_count_sub + < st->eis.voting_count)) { + st->eis.voting_state = 8; + } else if (st->eis.voting_count_sub > 5) { + st->eis.fsync_delay_s[1] = tmp1; + st->eis.voting_state = 6; + st->eis.voting_count = 1; + } + + } + break; + default: + break; + } + + pr_debug("de= %d gc= %d\n", delay, st->eis.gyro_counter); + st->eis.fsync_delay = delay; + st->eis.gyro_counter = 0; + + pr_debug("state=%d g1= %d d1= %d g2= %d d2= %d\n", + st->eis.voting_state, + st->eis.gyro_counter_s[0], + st->eis.fsync_delay_s[0], + st->eis.gyro_counter_s[1], + st->eis.fsync_delay_s[1]); + + return 0; +} + +int inv_rate_convert(struct inv_mpu_state *st, int ind, int data) +{ + int t, out, out1, out2; + int base_freq; + + if (data <= MPU_DEFAULT_DMP_FREQ) + base_freq = MPU_DEFAULT_DMP_FREQ; + else + base_freq = BASE_SAMPLE_RATE; + + t = base_freq / data; + if (!t) + t = 1; + out1 = base_freq / (t + 1); + out2 = base_freq / t; + if ((data - out1) * INV_ODR_BUFFER_MULTI < data) + out = out1; + else + out = out2; + + return out; +} + +static void inv_check_wake_non_wake(struct inv_mpu_state *st, + enum SENSOR_L wake, enum SENSOR_L non_wake) +{ + int tmp_rate; + + if (!st->sensor_l[wake].on && !st->sensor_l[non_wake].on) + return; + + tmp_rate = MPU_INIT_SENSOR_RATE; + if (st->sensor_l[wake].on) + tmp_rate = st->sensor_l[wake].rate; + if (st->sensor_l[non_wake].on) + tmp_rate = max(tmp_rate, st->sensor_l[non_wake].rate); + st->sensor_l[wake].rate = tmp_rate; + st->sensor_l[non_wake].rate = tmp_rate; +} + +static void inv_check_wake_non_wake_divider(struct inv_mpu_state *st, + enum SENSOR_L wake, enum SENSOR_L non_wake) +{ + if (st->sensor_l[wake].on && st->sensor_l[non_wake].on) + st->sensor_l[non_wake].div = 0xffff; + +} + +#if defined(CONFIG_INV_MPU_IIO_ICM20602) \ + || defined(CONFIG_INV_MPU_IIO_ICM20690) \ + || defined(CONFIG_INV_MPU_IIO_IAM20680) +int inv_check_sensor_on(struct inv_mpu_state *st) +{ + int i, max_rate; + enum SENSOR_L wake[] = {SENSOR_L_GYRO_WAKE, SENSOR_L_ACCEL_WAKE, + SENSOR_L_MAG_WAKE}; + enum SENSOR_L non_wake[] = {SENSOR_L_GYRO, SENSOR_L_ACCEL, + SENSOR_L_MAG}; + + st->sensor_l[SENSOR_L_GESTURE_ACCEL].rate = GESTURE_ACCEL_RATE; + for (i = 0; i < SENSOR_NUM_MAX; i++) + st->sensor[i].on = false; + for (i = 0; i < SENSOR_NUM_MAX; i++) + st->sensor[i].rate = MPU_INIT_SENSOR_RATE; + + if ((st->step_detector_l_on + || st->step_detector_wake_l_on + || st->step_counter_l_on + || st->step_counter_wake_l_on + || st->chip_config.pick_up_enable + || st->chip_config.tilt_enable) + && (!st->sensor_l[SENSOR_L_ACCEL].on) + && (!st->sensor_l[SENSOR_L_ACCEL_WAKE].on)) + st->sensor_l[SENSOR_L_GESTURE_ACCEL].on = true; + else + st->sensor_l[SENSOR_L_GESTURE_ACCEL].on = false; + + + st->chip_config.wake_on = false; + for (i = 0; i < SENSOR_L_NUM_MAX; i++) { + if (st->sensor_l[i].on && st->sensor_l[i].rate) { + st->sensor[st->sensor_l[i].base].on = true; + st->chip_config.wake_on |= st->sensor_l[i].wake_on; + } + } + if (st->sensor_l[SENSOR_L_GESTURE_ACCEL].on && + (!st->sensor[SENSOR_GYRO].on) && + (!st->sensor[SENSOR_COMPASS].on)) + st->gesture_only_on = true; + else + st->gesture_only_on = false; + + for (i = 0; i < SENSOR_L_NUM_MAX; i++) { + if (st->sensor_l[i].on) { + st->sensor[st->sensor_l[i].base].rate = + max(st->sensor[st->sensor_l[i].base].rate, + st->sensor_l[i].rate); + } + } + max_rate = MPU_INIT_SENSOR_RATE; + if (st->chip_config.eis_enable) { + max_rate = ESI_GYRO_RATE; + st->sensor_l[SENSOR_L_EIS_GYRO].rate = ESI_GYRO_RATE; + } + + for (i = 0; i < SENSOR_NUM_MAX; i++) { + if (st->sensor[i].on) { + max_rate = max(max_rate, st->sensor[i].rate); + } + } + for (i = 0; i < SENSOR_NUM_MAX; i++) { + if (st->sensor[i].on) { + st->sensor[i].rate = max_rate; + } + } + for (i = 0; i < ARRAY_SIZE(wake); i++) + inv_check_wake_non_wake(st, wake[i], non_wake[i]); + + for (i = 0; i < SENSOR_L_NUM_MAX; i++) { + if (st->sensor_l[i].on) { + if (st->sensor_l[i].rate) + st->sensor_l[i].div = + st->sensor[st->sensor_l[i].base].rate + / st->sensor_l[i].rate; + else + st->sensor_l[i].div = 0xffff; + pr_debug("sensor= %d, div= %d\n", + i, st->sensor_l[i].div); + } + } + for (i = 0; i < ARRAY_SIZE(wake); i++) + inv_check_wake_non_wake_divider(st, wake[i], non_wake[i]); + + if (st->step_detector_wake_l_on || + st->step_counter_wake_l_on || + st->chip_config.pick_up_enable || + st->chip_config.tilt_enable) + st->chip_config.wake_on = true; + + return 0; +} +#else +static void inv_do_check_sensor_on(struct inv_mpu_state *st, + enum SENSOR_L *wake, + enum SENSOR_L *non_wake, int sensor_size) +{ + int i; + + for (i = 0; i < SENSOR_NUM_MAX; i++) + st->sensor[i].on = false; + + for (i = 0; i < SENSOR_NUM_MAX; i++) + st->sensor[i].rate = MPU_INIT_SENSOR_RATE; + + st->chip_config.wake_on = false; + for (i = 0; i < SENSOR_L_NUM_MAX; i++) { + if (st->sensor_l[i].on && st->sensor_l[i].rate) { + st->sensor[st->sensor_l[i].base].on = true; + st->chip_config.wake_on |= st->sensor_l[i].wake_on; + } + } + + for (i = 0; i < SENSOR_L_NUM_MAX; i++) { + if (st->sensor_l[i].on) { + st->sensor[st->sensor_l[i].base].rate = + max(st->sensor[st->sensor_l[i].base].rate, + st->sensor_l[i].rate); + } + } + for (i = 0; i < sensor_size; i++) + inv_check_wake_non_wake(st, wake[i], non_wake[i]); + + for (i = 0; i < SENSOR_L_NUM_MAX; i++) { + if (st->sensor_l[i].on) { + if (st->sensor_l[i].rate) + st->sensor_l[i].div = + st->sensor[st->sensor_l[i].base].rate + / st->sensor_l[i].rate; + else + st->sensor_l[i].div = 0xffff; + } + } + for (i = 0; i < sensor_size; i++) + inv_check_wake_non_wake_divider(st, wake[i], non_wake[i]); + + if (st->step_detector_wake_l_on || + st->step_counter_wake_l_on || + st->chip_config.pick_up_enable || + st->chip_config.tilt_enable || + st->smd.on) + st->chip_config.wake_on = true; + +} +#endif + +#if defined(CONFIG_INV_MPU_IIO_ICM20608D) +int inv_check_sensor_on(struct inv_mpu_state *st) +{ + enum SENSOR_L wake[] = {SENSOR_L_GYRO_WAKE, SENSOR_L_ACCEL_WAKE, + SENSOR_L_SIXQ_WAKE, SENSOR_L_PEDQ_WAKE, + SENSOR_L_GYRO_CAL_WAKE}; + enum SENSOR_L non_wake[] = {SENSOR_L_GYRO, SENSOR_L_ACCEL, + SENSOR_L_SIXQ, SENSOR_L_PEDQ, + SENSOR_L_GYRO_CAL}; + + inv_do_check_sensor_on(st, wake, non_wake, ARRAY_SIZE(wake)); + + return 0; +} +#endif + +#if defined(CONFIG_INV_MPU_IIO_ICM20648) +int inv_check_sensor_on(struct inv_mpu_state *st) +{ + enum SENSOR_L wake[] = {SENSOR_L_GYRO_WAKE, SENSOR_L_ACCEL_WAKE, + SENSOR_L_MAG_WAKE, SENSOR_L_ALS_WAKE, + SENSOR_L_SIXQ_WAKE, SENSOR_L_PEDQ_WAKE, + SENSOR_L_NINEQ_WAKE, SENSOR_L_GEOMAG_WAKE, + SENSOR_L_PRESSURE_WAKE, + SENSOR_L_GYRO_CAL_WAKE, + SENSOR_L_MAG_CAL_WAKE}; + enum SENSOR_L non_wake[] = {SENSOR_L_GYRO, SENSOR_L_ACCEL, + SENSOR_L_MAG, SENSOR_L_ALS, + SENSOR_L_SIXQ, SENSOR_L_PEDQ, + SENSOR_L_NINEQ, SENSOR_L_GEOMAG, + SENSOR_L_PRESSURE, + SENSOR_L_GYRO_CAL, + SENSOR_L_MAG_CAL}; + + inv_do_check_sensor_on(st, wake, non_wake, ARRAY_SIZE(wake)); + + return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +int inv_mpu_suspend(struct iio_dev *indio_dev) +{ + struct inv_mpu_state *st = iio_priv(indio_dev); + + /* add code according to different request Start */ + dev_info(st->dev, "%s suspend\n", st->hw->name); + mutex_lock(&indio_dev->mlock); + + st->resume_state = false; + if (st->chip_config.wake_on) { + enable_irq_wake(st->irq); + } else { + inv_stop_interrupt(st); + } + + mutex_unlock(&indio_dev->mlock); + + return 0; +} +EXPORT_SYMBOL_GPL(inv_mpu_suspend); + +/* + * inv_mpu_complete(): complete method for this driver. + * This method can be modified according to the request of different + * customers. It basically undo everything suspend is doing + * and recover the chip to what it was before suspend. We use complete to + * make sure that alarm clock resume is finished. If we use resume, the + * alarm clock may not resume yet and get incorrect clock reading. + */ +void inv_mpu_complete(struct iio_dev *indio_dev) +{ + struct inv_mpu_state *st = iio_priv(indio_dev); + + dev_info(st->dev, "%s resume\n", st->hw->name); + if (st->resume_state) + return; + + mutex_lock(&indio_dev->mlock); + + if (!st->chip_config.wake_on) { + inv_reenable_interrupt(st); + } else { + disable_irq_wake(st->irq); + } + /* resume state is used to synchronize read_fifo such that it won't + proceed unless resume is finished. */ + st->resume_state = true; + /* resume flag is indicating that current clock reading is from resume, + it has up to 1 second drift and should do proper processing */ + st->ts_algo.resume_flag = true; + mutex_unlock(&indio_dev->mlock); + wake_up_interruptible(&st->wait_queue); + + return; +} +EXPORT_SYMBOL_GPL(inv_mpu_complete); +#endif diff --git a/drivers/iio/imu/inv_mpu/inv_mpu_dts.c b/drivers/iio/imu/inv_mpu/inv_mpu_dts.c new file mode 100644 index 000000000000..0b8b3fc29b0a --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_mpu_dts.c @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2012-2017 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/device.h> +#include <linux/err.h> +#include <linux/of_gpio.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regulator/consumer.h> +#include <linux/export.h> + +#include <linux/iio/imu/mpu.h> +#include "inv_mpu_dts.h" +#include "inv_mpu_iio.h" + +#ifdef CONFIG_OF + +static int inv_mpu_power_on(struct mpu_platform_data *pdata) +{ + int err; + + if (!IS_ERR(pdata->vdd_ana)) { + err = regulator_enable(pdata->vdd_ana); + if (err) + return err; + } + if (!IS_ERR(pdata->vdd_i2c)) { + err = regulator_enable(pdata->vdd_i2c); + if (err) + goto error_disable_vdd_ana; + } + + return 0; + +error_disable_vdd_ana: + regulator_disable(pdata->vdd_ana); + return err; +} + +static int inv_mpu_power_off(struct mpu_platform_data *pdata) +{ + if (!IS_ERR(pdata->vdd_ana)) + regulator_disable(pdata->vdd_ana); + if (!IS_ERR(pdata->vdd_i2c)) + regulator_disable(pdata->vdd_i2c); + + return 0; +} + +static int inv_parse_orientation_matrix(struct device *dev, s8 *orient) +{ + int rc, i; + struct device_node *np = dev->of_node; + u32 temp_val, temp_val2; + + for (i = 0; i < 9; i++) + orient[i] = 0; + + /* parsing axis x orientation matrix */ + rc = of_property_read_u32(np, "axis_map_x", &temp_val); + if (rc) { + dev_err(dev, "Unable to read axis_map_x\n"); + return rc; + } + rc = of_property_read_u32(np, "negate_x", &temp_val2); + if (rc) { + dev_err(dev, "Unable to read negate_x\n"); + return rc; + } + if (temp_val2) + orient[temp_val] = -1; + else + orient[temp_val] = 1; + + /* parsing axis y orientation matrix */ + rc = of_property_read_u32(np, "axis_map_y", &temp_val); + if (rc) { + dev_err(dev, "Unable to read axis_map_y\n"); + return rc; + } + rc = of_property_read_u32(np, "negate_y", &temp_val2); + if (rc) { + dev_err(dev, "Unable to read negate_y\n"); + return rc; + } + if (temp_val2) + orient[3 + temp_val] = -1; + else + orient[3 + temp_val] = 1; + + /* parsing axis z orientation matrix */ + rc = of_property_read_u32(np, "axis_map_z", &temp_val); + if (rc) { + dev_err(dev, "Unable to read axis_map_z\n"); + return rc; + } + rc = of_property_read_u32(np, "negate_z", &temp_val2); + if (rc) { + dev_err(dev, "Unable to read negate_z\n"); + return rc; + } + if (temp_val2) + orient[6 + temp_val] = -1; + else + orient[6 + temp_val] = 1; + + return 0; +} + +static int inv_parse_secondary_orientation_matrix(struct device *dev, + s8 *orient) +{ + int rc, i; + struct device_node *np = dev->of_node; + u32 temp_val, temp_val2; + + for (i = 0; i < 9; i++) + orient[i] = 0; + + /* parsing axis x orientation matrix */ + rc = of_property_read_u32(np, "inven,secondary_axis_map_x", &temp_val); + if (rc) { + dev_err(dev, "Unable to read secondary axis_map_x\n"); + return rc; + } + rc = of_property_read_u32(np, "inven,secondary_negate_x", &temp_val2); + if (rc) { + dev_err(dev, "Unable to read secondary negate_x\n"); + return rc; + } + if (temp_val2) + orient[temp_val] = -1; + else + orient[temp_val] = 1; + + /* parsing axis y orientation matrix */ + rc = of_property_read_u32(np, "inven,secondary_axis_map_y", &temp_val); + if (rc) { + dev_err(dev, "Unable to read secondary axis_map_y\n"); + return rc; + } + rc = of_property_read_u32(np, "inven,secondary_negate_y", &temp_val2); + if (rc) { + dev_err(dev, "Unable to read secondary negate_y\n"); + return rc; + } + if (temp_val2) + orient[3 + temp_val] = -1; + else + orient[3 + temp_val] = 1; + + /* parsing axis z orientation matrix */ + rc = of_property_read_u32(np, "inven,secondary_axis_map_z", &temp_val); + if (rc) { + dev_err(dev, "Unable to read secondary axis_map_z\n"); + return rc; + } + rc = of_property_read_u32(np, "inven,secondary_negate_z", &temp_val2); + if (rc) { + dev_err(dev, "Unable to read secondary negate_z\n"); + return rc; + } + if (temp_val2) + orient[6 + temp_val] = -1; + else + orient[6 + temp_val] = 1; + + return 0; +} + +static int inv_parse_secondary(struct device *dev, + struct mpu_platform_data *pdata) +{ + int rc; + struct device_node *np = dev->of_node; + u32 temp_val; + const char *name; + + if (of_property_read_string(np, "inven,secondary_type", &name)) { + dev_err(dev, "Missing secondary type.\n"); + return -EINVAL; + } + if (!strcmp(name, "compass")) { + pdata->sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS; + } else if (!strcmp(name, "none")) { + pdata->sec_slave_type = SECONDARY_SLAVE_TYPE_NONE; + return 0; + } else { + return -EINVAL; + } + + if (of_property_read_string(np, "inven,secondary_name", &name)) { + dev_err(dev, "Missing secondary name.\n"); + return -EINVAL; + } + if (!strcmp(name, "ak8963")) + pdata->sec_slave_id = COMPASS_ID_AK8963; + else if (!strcmp(name, "ak8975")) + pdata->sec_slave_id = COMPASS_ID_AK8975; + else if (!strcmp(name, "ak8972")) + pdata->sec_slave_id = COMPASS_ID_AK8972; + else if (!strcmp(name, "ak09911")) + pdata->sec_slave_id = COMPASS_ID_AK09911; + else if (!strcmp(name, "ak09912")) + pdata->sec_slave_id = COMPASS_ID_AK09912; + else if (!strcmp(name, "ak09916")) + pdata->sec_slave_id = COMPASS_ID_AK09916; + else + return -EINVAL; + rc = of_property_read_u32(np, "inven,secondary_reg", &temp_val); + if (rc) { + dev_err(dev, "Unable to read secondary register\n"); + return rc; + } + pdata->secondary_i2c_addr = temp_val; + rc = inv_parse_secondary_orientation_matrix(dev, + pdata-> + secondary_orientation); + + return rc; +} + +static int inv_parse_aux(struct device *dev, struct mpu_platform_data *pdata) +{ + int rc; + struct device_node *np = dev->of_node; + u32 temp_val; + const char *name; + + if (of_property_read_string(np, "inven,aux_type", &name)) { + dev_err(dev, "Missing aux type.\n"); + return -EINVAL; + } + if (!strcmp(name, "pressure")) { + pdata->aux_slave_type = SECONDARY_SLAVE_TYPE_PRESSURE; + } else if (!strcmp(name, "none")) { + pdata->aux_slave_type = SECONDARY_SLAVE_TYPE_NONE; + return 0; + } else { + return -EINVAL; + } + + if (of_property_read_string(np, "inven,aux_name", &name)) { + dev_err(dev, "Missing aux name.\n"); + return -EINVAL; + } + if (!strcmp(name, "bmp280")) + pdata->aux_slave_id = PRESSURE_ID_BMP280; + else + return -EINVAL; + + rc = of_property_read_u32(np, "inven,aux_reg", &temp_val); + if (rc) { + dev_err(dev, "Unable to read aux register\n"); + return rc; + } + pdata->aux_i2c_addr = temp_val; + + return 0; +} + +static int inv_parse_readonly_secondary(struct device *dev, + struct mpu_platform_data *pdata) +{ + int rc; + struct device_node *np = dev->of_node; + u32 temp_val; + const char *name; + + if (of_property_read_string(np, "inven,read_only_slave_type", &name)) { + dev_err(dev, "Missing read only slave type type.\n"); + return -EINVAL; + } + if (!strcmp(name, "als")) { + pdata->read_only_slave_type = SECONDARY_SLAVE_TYPE_ALS; + } else if (!strcmp(name, "none")) { + pdata->read_only_slave_type = SECONDARY_SLAVE_TYPE_NONE; + return 0; + } else { + return -EINVAL; + } + + if (of_property_read_string(np, "inven,read_only_slave_name", &name)) { + dev_err(dev, "Missing read only slave type name.\n"); + return -EINVAL; + } + if (!strcmp(name, "apds9930")) + pdata->read_only_slave_id = ALS_ID_APDS_9930; + else + return -EINVAL; + + rc = of_property_read_u32(np, "inven,read_only_slave_reg", &temp_val); + if (rc) { + dev_err(dev, "Unable to read read only slave reg register\n"); + return rc; + } + pdata->read_only_i2c_addr = temp_val; + + return 0; +} + +int invensense_mpu_parse_dt(struct device *dev, struct mpu_platform_data *pdata) +{ + int rc; + + rc = inv_parse_orientation_matrix(dev, pdata->orientation); + if (rc) + return rc; + rc = inv_parse_secondary(dev, pdata); + if (rc) + return rc; + inv_parse_aux(dev, pdata); + + inv_parse_readonly_secondary(dev, pdata); + + pdata->vdd_ana = regulator_get(dev, "inven,vdd_ana"); + if (IS_ERR(pdata->vdd_ana)) { + rc = PTR_ERR(pdata->vdd_ana); + dev_warn(dev, "regulator get failed vdd_ana-supply rc=%d\n", rc); + } + pdata->vdd_i2c = regulator_get(dev, "inven,vcc_i2c"); + if (IS_ERR(pdata->vdd_i2c)) { + rc = PTR_ERR(pdata->vdd_i2c); + dev_warn(dev, "regulator get failed vcc-i2c-supply rc=%d\n", rc); + } + pdata->power_on = inv_mpu_power_on; + pdata->power_off = inv_mpu_power_off; + dev_dbg(dev, "parse dt complete\n"); + + return 0; +} +EXPORT_SYMBOL_GPL(invensense_mpu_parse_dt); + +#endif /* CONFIG_OF */ diff --git a/drivers/iio/imu/inv_mpu/inv_mpu_dts.h b/drivers/iio/imu/inv_mpu/inv_mpu_dts.h new file mode 100644 index 000000000000..90966febb930 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_mpu_dts.h @@ -0,0 +1,25 @@ +/* +* Copyright (C) 2012-2017 InvenSense, Inc. +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ + +#ifndef _INV_MPU_DTS_H_ +#define _INV_MPU_DTS_H_ + +#include <linux/kernel.h> +#include <linux/iio/imu/mpu.h> + +#ifdef CONFIG_OF +int invensense_mpu_parse_dt(struct device *dev, + struct mpu_platform_data *pdata); +#endif + +#endif /* #ifndef _INV_MPU_DTS_H_ */ diff --git a/drivers/iio/imu/inv_mpu/inv_mpu_i2c.c b/drivers/iio/imu/inv_mpu/inv_mpu_i2c.c new file mode 100644 index 000000000000..e7838fce84a8 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_mpu_i2c.c @@ -0,0 +1,556 @@ +/* +* Copyright (C) 2012-2018 InvenSense, Inc. +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#define pr_fmt(fmt) "inv_mpu: " fmt + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/sysfs.h> +#include <linux/jiffies.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/poll.h> +#include <linux/miscdevice.h> +#include <linux/spinlock.h> + +#include "inv_mpu_iio.h" +#include "inv_mpu_dts.h" + +#define CONFIG_DYNAMIC_DEBUG_I2C 0 + +/** + * inv_i2c_read_base() - Read one or more bytes from the device registers. + * @st: Device driver instance. + * @i2c_addr: i2c address of device. + * @reg: First device register to be read from. + * @length: Number of bytes to read. + * @data: Data read from device. + * NOTE:This is not re-implementation of i2c_smbus_read because i2c + * address could be specified in this case. We could have two different + * i2c address due to secondary i2c interface. + */ +int inv_i2c_read_base(struct inv_mpu_state *st, u16 i2c_addr, + u8 reg, u16 length, u8 *data) +{ + struct i2c_msg msgs[2]; + int res; + + if (!data) + return -EINVAL; + + msgs[0].addr = i2c_addr; + msgs[0].flags = 0; /* write */ + msgs[0].buf = ® + msgs[0].len = 1; + + msgs[1].addr = i2c_addr; + msgs[1].flags = I2C_M_RD; + msgs[1].buf = data; + msgs[1].len = length; + + res = i2c_transfer(st->sl_handle, msgs, 2); + + if (res < 2) { + if (res >= 0) + res = -EIO; + } else + res = 0; + INV_I2C_INC_MPUWRITE(3); + INV_I2C_INC_MPUREAD(length); + + return res; +} + +/** + * inv_i2c_single_write_base() - Write a byte to a device register. + * @st: Device driver instance. + * @i2c_addr: I2C address of the device. + * @reg: Device register to be written to. + * @data: Byte to write to device. + * NOTE:This is not re-implementation of i2c_smbus_write because i2c + * address could be specified in this case. We could have two different + * i2c address due to secondary i2c interface. + */ +int inv_i2c_single_write_base(struct inv_mpu_state *st, + u16 i2c_addr, u8 reg, u8 data) +{ + u8 tmp[2]; + struct i2c_msg msg; + int res; + + tmp[0] = reg; + tmp[1] = data; + + msg.addr = i2c_addr; + msg.flags = 0; /* write */ + msg.buf = tmp; + msg.len = 2; + + INV_I2C_INC_MPUWRITE(3); + + res = i2c_transfer(st->sl_handle, &msg, 1); + if (res < 1) { + if (res == 0) + res = -EIO; + return res; + } else + return 0; +} + +static int inv_i2c_single_write(struct inv_mpu_state *st, u8 reg, u8 data) +{ + return inv_i2c_single_write_base(st, st->i2c_addr, reg, data); +} + +static int inv_i2c_read(struct inv_mpu_state *st, u8 reg, int len, u8 *data) +{ + return inv_i2c_read_base(st, st->i2c_addr, reg, len, data); +} + +static int _memory_write(struct inv_mpu_state *st, u8 mpu_addr, u16 mem_addr, + u32 len, u8 const *data) +{ + u8 bank[2]; + u8 addr[2]; + u8 buf[513]; + + struct i2c_msg msgs[3]; + int res; + + if (!data || !st) + return -EINVAL; + + if (len >= (sizeof(buf) - 1)) + return -ENOMEM; + + bank[0] = REG_MEM_BANK_SEL; + bank[1] = mem_addr >> 8; + + addr[0] = REG_MEM_START_ADDR; + addr[1] = mem_addr & 0xFF; + + buf[0] = REG_MEM_R_W; + memcpy(buf + 1, data, len); + + /* write message */ + msgs[0].addr = mpu_addr; + msgs[0].flags = 0; + msgs[0].buf = bank; + msgs[0].len = sizeof(bank); + + msgs[1].addr = mpu_addr; + msgs[1].flags = 0; + msgs[1].buf = addr; + msgs[1].len = sizeof(addr); + + msgs[2].addr = mpu_addr; + msgs[2].flags = 0; + msgs[2].buf = (u8 *) buf; + msgs[2].len = len + 1; + + INV_I2C_INC_MPUWRITE(3 + 3 + (2 + len)); + +#if CONFIG_DYNAMIC_DEBUG_I2C + { + char *write = 0; + pr_debug("%s WM%02X%02X%02X%s%s - %d\n", st->hw->name, + mpu_addr, bank[1], addr[1], + wr_pr_debug_begin(data, len, write), + wr_pr_debug_end(write), len); + } +#endif + + res = i2c_transfer(st->sl_handle, msgs, 3); + if (res != 3) { + if (res >= 0) + res = -EIO; + return res; + } else { + return 0; + } +} + +static int inv_i2c_mem_write(struct inv_mpu_state *st, u8 mpu_addr, u16 mem_addr, + u32 len, u8 const *data) +{ + int r, i, j; +#define DMP_MEM_CMP_SIZE 16 + u8 w[DMP_MEM_CMP_SIZE]; + bool retry; + + j = 0; + retry = true; + while ((j < 3) && retry) { + retry = false; + r = _memory_write(st, mpu_addr, mem_addr, len, data); + if (len < DMP_MEM_CMP_SIZE) { + r = mem_r(mem_addr, len, w); + for (i = 0; i < len; i++) { + if (data[i] != w[i]) { + pr_debug + ("error write=%x, len=%d,data=%x, w=%x, i=%d\n", + mem_addr, len, data[i], w[i], i); + retry = true; + } + } + } + j++; + } + + return r; +} + +static int inv_i2c_mem_read(struct inv_mpu_state *st, u8 mpu_addr, u16 mem_addr, + u32 len, u8 *data) +{ + u8 bank[2]; + u8 addr[2]; + u8 buf; + + struct i2c_msg msgs[4]; + int res; + + if (!data || !st) + return -EINVAL; + + bank[0] = REG_MEM_BANK_SEL; + bank[1] = mem_addr >> 8; + + addr[0] = REG_MEM_START_ADDR; + addr[1] = mem_addr & 0xFF; + + buf = REG_MEM_R_W; + + /* write message */ + msgs[0].addr = mpu_addr; + msgs[0].flags = 0; + msgs[0].buf = bank; + msgs[0].len = sizeof(bank); + + msgs[1].addr = mpu_addr; + msgs[1].flags = 0; + msgs[1].buf = addr; + msgs[1].len = sizeof(addr); + + msgs[2].addr = mpu_addr; + msgs[2].flags = 0; + msgs[2].buf = &buf; + msgs[2].len = 1; + + msgs[3].addr = mpu_addr; + msgs[3].flags = I2C_M_RD; + msgs[3].buf = data; + msgs[3].len = len; + + res = i2c_transfer(st->sl_handle, msgs, 4); + if (res != 4) { + if (res >= 0) + res = -EIO; + } else + res = 0; + INV_I2C_INC_MPUWRITE(3 + 3 + 3); + INV_I2C_INC_MPUREAD(len); + +#if CONFIG_DYNAMIC_DEBUG_I2C + { + char *read = 0; + pr_debug("%s RM%02X%02X%02X%02X - %s%s\n", st->hw->name, + mpu_addr, bank[1], addr[1], len, + wr_pr_debug_begin(data, len, read), + wr_pr_debug_end(read)); + } +#endif + + return res; +} + +/* + * inv_mpu_probe() - probe function. + */ +static int inv_mpu_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct inv_mpu_state *st; + struct iio_dev *indio_dev; + int result; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENOSYS; + pr_err("I2c function error\n"); + goto out_no_free; + } + +#ifdef KERNEL_VERSION_4_X + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (indio_dev == NULL) { + pr_err("memory allocation failed\n"); + result = -ENOMEM; + goto out_no_free; + } +#else + indio_dev = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + pr_err("memory allocation failed\n"); + result = -ENOMEM; + goto out_no_free; + } +#endif + st = iio_priv(indio_dev); + st->client = client; + st->sl_handle = client->adapter; + st->i2c_addr = client->addr; + st->write = inv_i2c_single_write; + st->read = inv_i2c_read; + st->mem_write = inv_i2c_mem_write; + st->mem_read = inv_i2c_mem_read; + st->dev = &client->dev; + st->bus_type = BUS_I2C; +#ifdef CONFIG_OF + result = invensense_mpu_parse_dt(st->dev, &st->plat_data); + if (result) +# ifdef KERNEL_VERSION_4_X + return -ENODEV; +# else + goto out_free; +# endif + + /* Power on device */ + if (st->plat_data.power_on) { + result = st->plat_data.power_on(&st->plat_data); + if (result < 0) { + dev_err(st->dev, "power_on failed: %d\n", result); +# ifdef KERNEL_VERSION_4_X + return -ENODEV; +# else + goto out_free; +# endif + } + pr_info("%s: power on here.\n", __func__); + } + pr_info("%s: power on.\n", __func__); + + msleep(100); +#else + if (dev_get_platdata(st->dev) == NULL) +# ifdef KERNEL_VERSION_4_X + return -ENODEV; +# else + goto out_free; +# endif + st->plat_data = *(struct mpu_platform_data *)dev_get_platdata(st->dev); +#endif + + /* power is turned on inside check chip type */ + result = inv_check_chip_type(indio_dev, id->name); + if (result) +#ifdef KERNEL_VERSION_4_X + return -ENODEV; +#else + goto out_free; +#endif + + /* Make state variables available to all _show and _store functions. */ + i2c_set_clientdata(client, indio_dev); + indio_dev->dev.parent = st->dev; + indio_dev->name = id->name; + + st->irq = client->irq; + + result = inv_mpu_configure_ring(indio_dev); + if (result) { + pr_err("configure ring buffer fail\n"); + goto out_free; + } +#ifdef KERNEL_VERSION_4_X + INV_I2C_SETIRQ(IRQ_MPU, st->irq); + result = devm_iio_device_register(st->dev, indio_dev); + if (result) { + pr_err("IIO device register fail\n"); + goto out_unreg_ring; + } +#else + result = iio_buffer_register(indio_dev, indio_dev->channels, + indio_dev->num_channels); + if (result) { + pr_err("ring buffer register fail\n"); + goto out_unreg_ring; + } + INV_I2C_SETIRQ(IRQ_MPU, client->irq); + result = iio_device_register(indio_dev); + if (result) { + pr_err("IIO device register fail\n"); + goto out_remove_ring; + } +#endif + + result = inv_create_dmp_sysfs(indio_dev); + if (result) { + pr_err("create dmp sysfs failed\n"); + goto out_unreg_iio; + } + init_waitqueue_head(&st->wait_queue); + st->resume_state = true; +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_init(&st->wake_lock, WAKE_LOCK_SUSPEND, "inv_mpu"); +#else + wakeup_source_init(&st->wake_lock, "inv_mpu"); +#endif + dev_info(st->dev, "%s ma-kernel-%s is ready to go!\n", + indio_dev->name, INVENSENSE_DRIVER_VERSION); + +#ifdef SENSOR_DATA_FROM_REGISTERS + pr_info("Data read from registers\n"); +#else + pr_info("Data read from FIFO\n"); +#endif +#ifdef TIMER_BASED_BATCHING + pr_info("Timer based batching\n"); +#endif + + return 0; +#ifdef KERNEL_VERSION_4_X +out_unreg_iio: + devm_iio_device_unregister(st->dev, indio_dev); +out_unreg_ring: + inv_mpu_unconfigure_ring(indio_dev); +out_free: + devm_iio_device_free(st->dev, indio_dev); +out_no_free: +#else +out_unreg_iio: + iio_device_unregister(indio_dev); +out_remove_ring: + iio_buffer_unregister(indio_dev); +out_unreg_ring: + inv_mpu_unconfigure_ring(indio_dev); +out_free: + iio_device_free(indio_dev); +out_no_free: +#endif + dev_err(st->dev, "%s failed %d\n", __func__, result); + + return -EIO; +} + +static void inv_mpu_shutdown(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct inv_mpu_state *st = iio_priv(indio_dev); + int result; + + mutex_lock(&indio_dev->mlock); + inv_switch_power_in_lp(st, true); + dev_dbg(st->dev, "Shutting down %s...\n", st->hw->name); + + /* reset to make sure previous state are not there */ + result = inv_plat_single_write(st, REG_PWR_MGMT_1, BIT_H_RESET); + if (result) + dev_err(st->dev, "Failed to reset %s\n", + st->hw->name); + msleep(POWER_UP_TIME); + /* turn off power to ensure gyro engine is off */ + result = inv_set_power(st, false); + if (result) + dev_err(st->dev, "Failed to turn off %s\n", + st->hw->name); + inv_switch_power_in_lp(st, false); + mutex_unlock(&indio_dev->mlock); +} + +/* + * inv_mpu_remove() - remove function. + */ +static int inv_mpu_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct inv_mpu_state *st = iio_priv(indio_dev); + +#ifdef KERNEL_VERSION_4_X + devm_iio_device_unregister(st->dev, indio_dev); +#else + iio_device_unregister(indio_dev); + iio_buffer_unregister(indio_dev); +#endif + inv_mpu_unconfigure_ring(indio_dev); +#ifdef KERNEL_VERSION_4_X + devm_iio_device_free(st->dev, indio_dev); +#else + iio_device_free(indio_dev); +#endif + dev_info(st->dev, "inv-mpu-iio module removed.\n"); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int inv_mpu_i2c_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + + return inv_mpu_suspend(indio_dev); +} + +static void inv_mpu_i2c_complete(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + + inv_mpu_complete(indio_dev); +} +#endif + +static const struct dev_pm_ops inv_mpu_i2c_pmops = { +#ifdef CONFIG_PM_SLEEP + .suspend = inv_mpu_i2c_suspend, + .complete = inv_mpu_i2c_complete, +#endif +}; + +/* device id table is used to identify what device can be + * supported by this driver + */ +static const struct i2c_device_id inv_mpu_id[] = { +#ifdef CONFIG_INV_MPU_IIO_ICM20648 + {"icm20645", ICM20645}, + {"icm10340", ICM10340}, + {"icm20648", ICM20648}, +#else + {"icm20608d", ICM20608D}, + {"icm20690", ICM20690}, + {"icm20602", ICM20602}, + {"iam20680", IAM20680}, +#endif + {} +}; + +MODULE_DEVICE_TABLE(i2c, inv_mpu_id); + +static struct i2c_driver inv_mpu_driver = { + .probe = inv_mpu_probe, + .remove = inv_mpu_remove, + .shutdown = inv_mpu_shutdown, + .id_table = inv_mpu_id, + .driver = { + .owner = THIS_MODULE, + .name = "inv-mpu-iio-i2c", + .pm = &inv_mpu_i2c_pmops, + }, +}; +module_i2c_driver(inv_mpu_driver); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense I2C device driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/inv_mpu/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu/inv_mpu_iio.h new file mode 100644 index 000000000000..9e7316558eae --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_mpu_iio.h @@ -0,0 +1,1138 @@ +/* + * Copyright (C) 2012-2018 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _INV_MPU_IIO_H_ +#define _INV_MPU_IIO_H_ + +#include <linux/version.h> +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,0,0)) +#define KERNEL_VERSION_4_X +#endif + +#include <linux/i2c.h> +#include <linux/kfifo.h> +#include <linux/miscdevice.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/iio/imu/mpu.h> +#include <linux/interrupt.h> +#include <linux/semaphore.h> +#ifdef CONFIG_HAS_WAKELOCK +#include <linux/wakelock.h> +#else +#include <linux/pm_wakeup.h> +#endif +#include <linux/wait.h> + +#include <linux/iio/sysfs.h> +#include <linux/iio/iio.h> +#include <linux/iio/kfifo_buf.h> + +#ifdef CONFIG_INV_MPU_IIO_ICM20648 +#include "icm20648/dmp3Default.h" +#endif +#ifdef CONFIG_INV_MPU_IIO_ICM20608D +#include "icm20608d/dmp3Default_20608D.h" +#endif + +#include "inv_test/inv_counters.h" + +#if defined(CONFIG_INV_MPU_IIO_ICM20648) +#include "icm20648/inv_mpu_iio_reg_20648.h" +#elif defined(CONFIG_INV_MPU_IIO_ICM20602) +#include "icm20602/inv_mpu_iio_reg_20602.h" +#elif defined(CONFIG_INV_MPU_IIO_ICM20608D) +#include "icm20608d/inv_mpu_iio_reg_20608.h" +#elif defined(CONFIG_INV_MPU_IIO_ICM20690) +#include "icm20690/inv_mpu_iio_reg_20690.h" +#elif defined(CONFIG_INV_MPU_IIO_IAM20680) +#include "iam20680/inv_mpu_iio_reg_20680.h" +#endif + +#define INVENSENSE_DRIVER_VERSION "8.1.2-simple-test1" + +/* #define DEBUG */ + +/* #define ACCEL_BIAS_TEST */ + +/* #define BIAS_CONFIDENCE_HIGH 1 */ + +#define MAX_FIFO_READ_SIZE 128 +#define MAX_DMP_READ_SIZE 16 + +/* data header defines */ +#define WAKE_HDR 0x8000 + +#define ACCEL_HDR 1 +#define GYRO_HDR 2 +#define COMPASS_HDR 3 +#define ALS_HDR 4 +#define SIXQUAT_HDR 5 +#define NINEQUAT_HDR 6 +#define PEDQUAT_HDR 7 +#define GEOMAG_HDR 8 +#define PRESSURE_HDR 9 +#define GYRO_CALIB_HDR 10 +#define COMPASS_CALIB_HDR 11 +#define STEP_COUNTER_HDR 12 +#define STEP_DETECTOR_HDR 13 +#define STEP_COUNT_HDR 14 +#define ACTIVITY_HDR 15 +#define PICK_UP_HDR 16 +#define EMPTY_MARKER 17 +#define END_MARKER 18 +#define COMPASS_ACCURACY_HDR 19 +#define ACCEL_ACCURACY_HDR 20 +#define GYRO_ACCURACY_HDR 21 +#define EIS_GYRO_HDR 36 +#define EIS_CALIB_HDR 37 +#define LPQ_HDR 38 + +#define ACCEL_WAKE_HDR (ACCEL_HDR | WAKE_HDR) +#define GYRO_WAKE_HDR (GYRO_HDR | WAKE_HDR) +#define COMPASS_WAKE_HDR (COMPASS_HDR | WAKE_HDR) +#define ALS_WAKE_HDR (ALS_HDR | WAKE_HDR) +#define SIXQUAT_WAKE_HDR (SIXQUAT_HDR | WAKE_HDR) +#define NINEQUAT_WAKE_HDR (NINEQUAT_HDR | WAKE_HDR) +#define PEDQUAT_WAKE_HDR (PEDQUAT_HDR | WAKE_HDR) +#define GEOMAG_WAKE_HDR (GEOMAG_HDR | WAKE_HDR) +#define PRESSURE_WAKE_HDR (PRESSURE_HDR | WAKE_HDR) +#define GYRO_CALIB_WAKE_HDR (GYRO_CALIB_HDR | WAKE_HDR) +#define COMPASS_CALIB_WAKE_HDR (COMPASS_CALIB_HDR | WAKE_HDR) +#define STEP_COUNTER_WAKE_HDR (STEP_COUNTER_HDR | WAKE_HDR) +#define STEP_DETECTOR_WAKE_HDR (STEP_DETECTOR_HDR | WAKE_HDR) + +/* init parameters */ +#define MPU_INIT_SMD_THLD 1500 +#define MPU_INIT_GYRO_SCALE 3 +#define MPU_INIT_ACCEL_SCALE 2 +#define MPU_INIT_PED_INT_THRESH 2 +#define MPU_INIT_PED_STEP_THRESH 6 +#define MPU_4X_TS_GYRO_SHIFT (3160000 / 2) +#define DMP_START_ADDR_20645 0x900 +#define DMP_START_ADDR_20648 0x1000 +#define DMP_START_ADDR_10340 0x0a60 +#define DMP_START_ADDR_20608D 0x4B0 +#define MAX_WR_SZ 100 +#define WOM_DELAY_THRESHOLD 200 +#define INV_ODR_BUFFER_MULTI 20 +#define INV_ODR_OVER_FACTOR 20 + +#define COVARIANCE_SIZE 14 +#define ACCEL_COVARIANCE_SIZE (COVARIANCE_SIZE * sizeof(int)) + +enum inv_bus_type { + BUS_I2C = 0, + BUS_SPI, +}; + +struct inv_mpu_state; + +enum INV_ENGINE { + ENGINE_GYRO = 0, + ENGINE_ACCEL, + ENGINE_PRESSURE, + ENGINE_I2C, + ENGINE_NUM_MAX, +}; + +/** + * struct inv_hw_s - Other important hardware information. + * @num_reg: Number of registers on device. + * @name: name of the chip + */ +struct inv_hw_s { + u8 num_reg; + u8 *name; +}; + +/** + * struct inv_sensor - information for each sensor. + * @ts: this sensors timestamp. + * @ts_adj: sensor timestamp adjustment. + * @previous_ts: previous timestamp for this sensor. + * @dur: duration between samples in ns. + * @rate: sensor data rate. + * @sample_size: number of bytes for the sensor. + * @odr_addr: output data rate address in DMP. + * @counter_addr: output counter address in DMP. + * @output: output on/off control word. + * @time_calib: calibrate timestamp. + * @sample_calib: calibrate bytes accumulated. + * @div: divider in DMP mode. + * @calib_flag: calibrate flag used to improve the accuracy of estimation. + * @on: sensor on/off. + * @a_en: accel engine requirement. + * @g_en: gyro engine requirement. + * @c_en: compass_engine requirement. + * @p_en: pressure engine requirement. + * @engine_base: engine base for this sensor. + * @count: number of samples in one session. + * @send: decide whether to send this sample or not. + */ +struct inv_sensor { + u64 ts; + s64 ts_adj; + u64 previous_ts; + int dur; + int rate; + u8 sample_size; + int odr_addr; + int counter_addr; + u16 output; + u64 time_calib; + u32 sample_calib; + int div; + bool calib_flag; + bool on; + bool a_en; + bool g_en; + bool c_en; + bool p_en; + enum INV_ENGINE engine_base; + int count; + bool send; +}; + +/** + * struct inv_sensor - information for each sensor. + * @sample_size: number of bytes for the sensor. + * @output: output on/off control word. + * @on: sensor on/off. + * @header: accuracy header for communicate with HAL + *dd @count: number of samples in one session. + */ +struct inv_sensor_accuracy { + u16 output; + u8 sample_size; + bool on; + u16 header; +}; + +enum SENSOR_ACCURACY { + SENSOR_ACCEL_ACCURACY = 0, + SENSOR_GYRO_ACCURACY, + SENSOR_COMPASS_ACCURACY, + SENSOR_ACCURACY_NUM_MAX, +}; + +enum SENSOR_L { + SENSOR_L_ACCEL = 0, + SENSOR_L_GYRO, + SENSOR_L_MAG, + SENSOR_L_ALS, + SENSOR_L_SIXQ, + SENSOR_L_THREEQ, + SENSOR_L_NINEQ, + SENSOR_L_PEDQ, + SENSOR_L_GEOMAG, + SENSOR_L_PRESSURE, + SENSOR_L_GYRO_CAL, + SENSOR_L_MAG_CAL, + SENSOR_L_EIS_GYRO, + /*wake sensors */ + SENSOR_L_ACCEL_WAKE = 13, + SENSOR_L_GYRO_WAKE, + SENSOR_L_MAG_WAKE, + SENSOR_L_ALS_WAKE, + SENSOR_L_SIXQ_WAKE, + SENSOR_L_NINEQ_WAKE, + SENSOR_L_PEDQ_WAKE, + SENSOR_L_GEOMAG_WAKE, + SENSOR_L_PRESSURE_WAKE, + SENSOR_L_GYRO_CAL_WAKE, + SENSOR_L_MAG_CAL_WAKE, + SENSOR_L_GESTURE_ACCEL, + SENSOR_L_NUM_MAX, +}; + +/** + * struct android_l_sensor - information for each android sensor. + * @ts: this sensors timestamp. + * @base: android sensor based on invensense sensor. + * @rate: output rate. + * @on: sensor on/off. + * @wake_on: wake on sensor is on/off. + * @div: divider for the output. + * @counter: counter works with the divider. + * @header: header for the output. + */ +struct android_l_sensor { + u64 ts; + enum INV_SENSORS base; + int rate; + bool on; + bool wake_on; + int div; + int counter; + u16 header; +}; + +/** + * struct inv_batch - information for batchmode. + * @on: normal batch mode on. + * @default_on: default batch on. This is optimization option. + * @overflow_on: overflow mode for batchmode. + * @wake_fifo_on: overflow for suspend mode. + * @step_only: mean only step detector data is batched. + * @post_isr_run: mean post isr has runned once. + * @counter: counter for batch mode. + * @timeout: nominal timeout value for batchmode in milliseconds. + * @max_rate: max rate for all batched sensors. + * @pk_size: packet size; + * @engine_base: engine base batch mode should stick to. + */ +struct inv_batch { + bool on; + bool default_on; + bool overflow_on; + bool wake_fifo_on; + bool step_only; + bool post_isr_run; + u32 counter; + u32 timeout; + u32 max_rate; + u32 pk_size; + u32 fifo_wm_th; + enum INV_ENGINE engine_base; +}; + +/** + * struct inv_chip_config_s - Cached chip configuration data. + * @fsr: Full scale range. + * @lpf: Digital low pass filter frequency. + * @accel_fs: accel full scale range. + * @accel_enable: enable accel functionality + * @gyro_enable: enable gyro functionality + * @compass_enable: enable compass functinality. + * @geomag_enable: enable geomag sensor functions. + * @als_enable: enable ALS functionality. + * @pressure_enable: eanble pressure functionality. + * @secondary_enable: secondary I2C bus enabled or not. + * @has_gyro: has gyro or not. + * @has_compass: has secondary I2C compass or not. + * @has_pressure: has secondary I2C pressure or not. + * @has_als: has secondary I2C als or not. + * @slave_enable: secondary I2C interface enabled or not. + * @normal_compass_measure: discard first compass data after reset. + * @is_asleep: 1 if chip is powered down. + * @lp_en_set: 1 if LP_EN bit is set; + * @lp_en_mode_off: debug mode that turns off LP_EN mode off. + * @clk_sel: debug_mode that turns on/off clock selection. + * @dmp_on: dmp is on/off. + * @dmp_event_int_on: dmp event interrupt on/off. + * @wom_on: WOM interrupt on. This is an internal variable. + * @step_indicator_on: step indicate bit added to the sensor or not. + * @tilt_enable: tilt enable. + * @pick_up_enable: pick up gesture enable. + * @step_detector_on: step detector on or not. + * @activity_on: turn on/off activity. + * @activity_eng_on: activity engine on/off. + * @firmware_loaded: flag indicate firmware loaded or not. + * @low_power_gyro_on: flag indicating low power gyro on/off. + * @wake_on: any wake on sensor is on/off. + * @compass_rate: compass engine rate. Determined by underlying data. + */ +struct inv_chip_config_s { + u32 fsr:2; + u32 lpf:3; + u32 accel_fs:2; + u32 accel_enable:1; + u32 gyro_enable:1; + u32 compass_enable:1; + u32 geomag_enable:1; + u32 als_enable:1; + u32 prox_enable:1; + u32 pressure_enable:1; + u32 has_gyro:1; + u32 has_compass:1; + u32 has_pressure:1; + u32 has_als:1; + u32 slave_enable:1; + u32 normal_compass_measure:1; + u32 is_asleep:1; + u32 lp_en_set:1; + u32 lp_en_mode_off:1; + u32 clk_sel:1; + u32 dmp_on:1; + u32 dmp_event_int_on:1; + u32 wom_on:1; + u32 step_indicator_on:1; + u32 tilt_enable:1; + u32 pick_up_enable:1; + u32 eis_enable:1; + u32 step_detector_on:1; + u32 activity_on:1; + u32 activity_eng_on:1; + u32 firmware_loaded:1; + u32 low_power_gyro_on:1; + u32 wake_on:1; + int compass_rate; +}; + +/** + * struct inv_temp_comp - temperature compensation structure. + * @t_lo: raw temperature in low temperature. + * @t_hi: raw temperature in high temperature. + * @b_lo: gyro bias in low temperature. + * @b_hi: gyro bias in high temperature. + * @has_low: flag indicate low temperature parameters is updated. + * @has_high: flag indicates high temperature parameters is updated. + * @slope: slope for temperature compensation. + */ +struct inv_temp_comp { + int t_lo; + int t_hi; + int b_lo[3]; + int b_hi[3]; + bool has_low; + bool has_high; + int slope[3]; +}; + +/** + * struct inv_chip_info_s - Chip related information. + * @product_id: Product id. + * @product_revision: Product revision. + * @silicon_revision: Silicon revision. + * @software_revision: software revision. + * @compass_sens: compass sensitivity. + * @gyro_sens_trim: Gyro sensitivity trim factor. + * @accel_sens_trim: accel sensitivity trim factor. + */ +struct inv_chip_info_s { + u8 product_id; + u8 product_revision; + u8 silicon_revision; + u8 software_revision; + u8 compass_sens[3]; + u32 gyro_sens_trim; + u32 accel_sens_trim; +}; + +/** + * struct inv_smd significant motion detection structure. + * @threshold: accel threshold for motion detection. + * @delay: delay time to confirm 2nd motion. + * @delay2: delay window parameter. + * @on: smd on/off. + */ +struct inv_smd { + u32 threshold; + u32 delay; + u32 delay2; + bool on; +}; + +/** + * struct inv_ped pedometer related data structure. + * @step: steps taken. + * @time: time taken during the period. + * @last_step_time: last time the step is taken. + * @step_thresh: step threshold to show steps. + * @int_thresh: step threshold to generate interrupt. + * @int_on: pedometer interrupt enable/disable. + * @on: pedometer on/off. + * @engine_on: pedometer engine on/off. + */ +struct inv_ped { + u64 step; + u64 time; + u64 last_step_time; + u16 step_thresh; + u16 int_thresh; + bool int_on; + bool on; + bool engine_on; +}; + +/** + * struct inv_eis EIS related data structure. + * @prev_gyro: latest gyro data just before FSYNC triggerd + * @prev_timestamp: latest gyro timestamp just before FSYNC triggered + * @current_gyro: gyro data just after FSYNC triggerd + * @current_timestamp: gyro timestamp just after FSYNC triggered + * @fsync_timestamp: timestamp of FSYNC event + * @fsync_delay: delay time of FSYNC and Gyro data. DMP data of FSYNC event + * @eis_triggered: check fsync event is triggered or not. + * @eis_frame: current frame is eis frame; + * @current_sync: current frame contains fsync counter. + * @frame_count: frame count for synchronization. + */ +struct inv_eis { + int prev_gyro[3]; + u64 prev_timestamp; + int current_gyro[3]; + u64 current_timestamp; + u32 frame_dur; + u64 slope[3]; + u64 fsync_timestamp; + u64 last_fsync_timestamp; + u16 fsync_delay; + bool eis_triggered; + bool eis_frame; + bool current_sync; + bool prev_state; + u32 frame_count; + int gyro_counter; + int gyro_counter_s[3]; + int fsync_delay_s[3]; + int voting_count; + int voting_count_sub; + int voting_state; + int count_precision; +}; + +enum TRIGGER_STATE { + DATA_TRIGGER = 0, + RATE_TRIGGER, + EVENT_TRIGGER, + MISC_TRIGGER, + DEBUG_TRIGGER, +}; + +enum inv_fifo_count_mode { + BYTE_MODE, + RECORD_MODE +}; + +/** + * struct inv_secondary_reg - secondary registers data structure. + * @addr: address of the slave. + * @reg: register address of slave. + * @ctrl: control register. + * @d0: data out register. + */ +struct inv_secondary_reg { + u8 addr; + u8 reg; + u8 ctrl; + u8 d0; +}; + +struct inv_secondary_set { + u8 delay_enable; + u8 delay_time; + u8 odr_config; +}; +/** + * struct inv_engine_info - data structure for engines. + * @base_time: base time for each engine. + * @base_time_1k: base time when chip is running at 1K; + * @divider: divider used to downsample engine rate from original rate. + * @running_rate: the actually running rate of engine. + * @orig_rate: original rate for each engine before downsample. + * @dur: duration for one tick. + * @last_update_time: last update time. + */ +struct inv_engine_info { + u32 base_time; + u32 base_time_1k; + u32 divider; + u32 running_rate; + u32 orig_rate; + u32 dur; + u64 last_update_time; +}; + +struct inv_ois { + int gyro_fs; + int accel_fs; + bool en; +}; + +/** + * struct inv_timestamp_algo - timestamp algorithm . + * @last_run_time: last time the post ISR runs. + * @ts_for_calib: ts storage for calibration. + * @reset_ts: reset time. + * @dmp_ticks: dmp ticks storage for calibration. + * @start_dmp_counter: dmp counter when start a new session. + * @calib_counter: calibration counter for timestamp. + * @resume_flag: flag to indicate this is the first time after resume. time + could have up to 1 seconds difference. + * @clock_base: clock base to calculate the timestamp. + * @gyro_ts_shift: 9 K counter for EIS. + * @first_sample: first of 1K running should be dropped it affects timing + */ +struct inv_timestamp_algo { + u64 last_run_time; + u64 ts_for_calib; + u64 reset_ts; + u32 dmp_ticks; + u32 start_dmp_counter; + int calib_counter; + bool resume_flag; + enum INV_ENGINE clock_base; + u32 gyro_ts_shift; + u32 first_sample; +}; + +struct inv_mpu_slave; +/** + * struct inv_mpu_state - Driver state variables. + * @dev: device address of the current bus, i2c or spi. + * @chip_config: Cached attribute information. + * @chip_info: Chip information from read-only registers. + * @smd: SMD data structure. + * @ped: pedometer data structure. + * @batch: batchmode data structure. + * @temp_comp: gyro temperature compensation structure. + * @slave_compass: slave compass. + * @slave_pressure: slave pressure. + * @slave_als: slave als. + * @slv_reg: slave register data structure. + * @ts_algo: timestamp algorithm data structure. + * @sec_set: slave register odr config. + * @eng_info: information for each engine. + * @hw: Other hardware-specific information. + * @chip_type: chip type. + * @suspend_resume_sema: semaphore for suspend/resume. + * @wake_lock: wake lock of the system. + * @client: i2c client handle. + * @plat_data: platform data. + * @sl_handle: Handle to I2C port. + * @sensor{SENSOR_NUM_MAX]: sensor individual properties. + * @sensor_l[SENSOR_L_NUM_MAX]: android L sensors properties. + * @sensor_accuracy[SENSOR_ACCURACY_NUM_MAX]: sensor accuracy. + * @sensor_acurracy_flag: flag indiciate whether to check output accuracy. + * @irq: irq number store. + * @accel_bias: accel bias store. + * @gyro_bias: gyro bias store. + * @accel_st_bias: accel bias store, result of self-test. + * @gyro_st_bias: gyro bias store, result of self-test. + * @gyro_ois_st_bias: gyro bias store from ois self test result. + * @input_accel_dmp_bias[3]: accel bias for dmp. + * @input_gyro_dmp_bias[3]: gyro bias for dmp. + * @input_compass_dmp_bias[3]: compass bias for dmp. + * @input_accel_bias[3]: accel bias for offset register. + * @input_gyro_bias[3]: gyro bias for offset register. + * @fifo_data[8]: fifo data storage. + * @i2c_addr: i2c address. + * @header_count: header count in current FIFO. + * @step_det_count: number of step detectors in one batch. + * @gyro_sf: gyro scale factor. + * @left_over[LEFT_OVER_BYTES]: left over bytes storage. + * @left_over_size: left over size. + * @fifo_count: current fifo_count; + * @wake_sensor_received: wake up sensor received. + * @accel_cal_enable: accel calibration on/off + * @gyro_cal_enable: gyro calibration on/off + * @calib_compass_on: calibrate compass on. + * @debug_determine_engine_on: determine engine on/off. + * @poke_mode_on: poke mode on/off. + * @mode_1k_on: indicate 1K Hz mode is on. + * @poke_ts: time stamp for poke feature. + * @step_detector_base_ts: base time stamp for step detector calculation. + * @last_temp_comp_time: last time temperature compensation is done. + * @i2c_dis: disable I2C interface or not. + * @name: name for the chip. + * @gyro_st_data: gyro self test data. + * @accel_st_data: accel self test data. + * @secondary_name: name for the slave device in the secondary I2C. + * @compass_var: compass variance from DMP. + * @current_compass_matrix: matrix compass data multiplied to before soft iron. + * @final_compass_matrix: matrix compass data multiplied to before soft iron. + * @trigger_state: information that which part triggers set_inv_enable. + * @firmware: firmware data pointer. + * @accel_calib_threshold: accel calibration threshold; + * @accel_calib_rate: divider for accel calibration rate. + * @accel_covariance[COVARIANCE_SIZE]: accel covariance data; + * @kf: kfifo for activity store. + * @activity_size: size for activity. + * @cntl: control word for sensor enable. + * @cntl2: control word for sensor extension. + * @motion_event_cntl: control word for events. + * @dmp_image_size: dmp image size. + * @dmp_start_address: start address of dmp. + * @step_counter_l_on: step counter android L sensor on/off. + * @step_counter_wake_l_on: step counter android L sensor wake on/off . + * @step_detector_l_on: step detector android L sensor on/off. + * @step_detector_wake_l_on: step detector android L sensor wake on/off . + * @gesture_only_on: indicate it is gesture only. + * @mag_divider: mag divider when gyro/accel is faster than mag maximum rate. + * @special_mag_mode: for 20690, there is special mag mode need to be handled. + * @mag_start_flag: when mag divider is non zero, need to check the start. + * @prev_steps: previous steps sent to the user. + * @aut_key_in: authentication key input. + * @aut_key_out: authentication key output. + * @suspend_state: state variable to indicate that we are in suspend state. + * @secondary_gyro_on: DMP out signal to turn on gyro. + * @secondary_mag_on: DMP out signal to turn on mag. + * @secondary_prox_on: DMP out signal to turn on proximity. + * @secondary_switch: showing this setup is triggerred by secondary switch. + * @send_calib_gyro: flag to indicate to send calibrated gyro. + * @send_raw_compass: flag to send raw compass. + * @resume_state: flag to synchronize the processing of inv_read_fifo() + * @cycle_on: variable indicate accel cycle mode is on. + * @secondary_switch_data: secondary switch data for activity. + * @raw_gyro_data[6]: save raw gyro data. + * @raw_compass_data[3]: save raw compass data. + * @wait_queue: wait queue to wake up inv_read_fifo() + * @bac_drive_conf: bac drive configuration. + * @bac_walk_conf: bac walk configuration. + * @bac_smd_conf: bac smd configuration. + * @bac_bike_conf: bac bike configuration. + * @bac_run_conf: bac run configuration. + * @bac_still_conf: back still configuration. + * @power_on_data: power on data. + * @fifo_data_store: store of FIFO data. + * @int_en: store interrupt enable register data. + * @int_en2: store interrupt enable register 2 data. + * @gesture_int_count: interrupt count for gesture only mode. + * @smplrt_div: SMPLRT_DIV register value. + */ +struct inv_mpu_state { + struct device *dev; + int (*write)(struct inv_mpu_state *st, u8 reg, u8 data); + int (*read)(struct inv_mpu_state *st, u8 reg, int len, u8 *data); + int (*mem_write)(struct inv_mpu_state *st, u8 mpu_addr, u16 mem_addr, + u32 len, u8 const *data); + int (*mem_read)(struct inv_mpu_state *st, u8 mpu_addr, u16 mem_addr, + u32 len, u8 *data); + struct inv_chip_config_s chip_config; + struct inv_chip_info_s chip_info; + struct inv_smd smd; + struct inv_ped ped; + struct inv_eis eis; + struct inv_batch batch; + struct inv_temp_comp temp_comp; + struct inv_mpu_slave *slave_compass; + struct inv_mpu_slave *slave_pressure; + struct inv_mpu_slave *slave_als; + struct inv_secondary_reg slv_reg[4]; + struct inv_timestamp_algo ts_algo; + struct inv_secondary_set sec_set; + struct inv_engine_info eng_info[ENGINE_NUM_MAX]; + const struct inv_hw_s *hw; + enum inv_devices chip_type; + enum inv_bus_type bus_type; + enum inv_fifo_count_mode fifo_count_mode; +#ifdef CONFIG_HAS_WAKELOCK + struct wake_lock wake_lock; +#else + struct wakeup_source wake_lock; +#endif +#ifdef TIMER_BASED_BATCHING + struct hrtimer hr_batch_timer; + u64 batch_timeout; + bool is_batch_timer_running; + struct work_struct batch_work; +#endif + struct i2c_client *client; + struct mpu_platform_data plat_data; + void *sl_handle; + struct inv_sensor sensor[SENSOR_NUM_MAX]; + struct android_l_sensor sensor_l[SENSOR_L_NUM_MAX]; + struct inv_sensor_accuracy sensor_accuracy[SENSOR_ACCURACY_NUM_MAX]; + struct inv_ois ois; + bool sensor_acurracy_flag[SENSOR_ACCURACY_NUM_MAX]; + short irq; + int accel_bias[3]; + int gyro_bias[3]; + int accel_st_bias[3]; + int accel_ois_st_bias[3]; + int gyro_st_bias[3]; + int gyro_ois_st_bias[3]; + int input_accel_dmp_bias[3]; + int input_gyro_dmp_bias[3]; + int input_compass_dmp_bias[3]; + int input_accel_bias[3]; + int input_gyro_bias[3]; + u8 fifo_data[8]; + u8 i2c_addr; + int header_count; + int step_det_count; + s32 gyro_sf; + u8 left_over[LEFT_OVER_BYTES]; + u32 left_over_size; + u32 fifo_count; + bool wake_sensor_received; + bool accel_cal_enable; + bool gyro_cal_enable; + bool calib_compass_on; + bool debug_determine_engine_on; + bool poke_mode_on; + bool mode_1k_on; + u64 poke_ts; + u64 step_detector_base_ts; + u64 last_temp_comp_time; + u8 i2c_dis; + u8 name[20]; + u8 gyro_st_data[3]; + u8 accel_st_data[3]; + u8 secondary_name[20]; + s32 compass_var; + int current_compass_matrix[9]; + int final_compass_matrix[9]; + enum TRIGGER_STATE trigger_state; + u8 *firmware; + int accel_calib_threshold; + int accel_calib_rate; + u32 accel_covariance[COVARIANCE_SIZE]; + DECLARE_KFIFO(kf, u8, 128); + u32 activity_size; + int wom_thld; + u16 cntl; + u16 cntl2; + u16 motion_event_cntl; + int dmp_image_size; + int dmp_start_address; + bool step_counter_l_on; + bool step_counter_wake_l_on; + bool step_detector_l_on; + bool step_detector_wake_l_on; + bool gesture_only_on; + bool mag_start_flag; + int mag_divider; + bool special_mag_mode; + int prev_steps; + u32 curr_steps; + int aut_key_in; + int aut_key_out; + bool secondary_gyro_on; + bool secondary_mag_on; + bool secondary_prox_on; + bool secondary_switch; + bool send_calib_gyro; + bool send_raw_compass; + bool send_raw_gyro; + bool resume_state; + bool cycle_on; + int secondary_switch_data; + u8 raw_gyro_data[6]; + u32 raw_compass_data[3]; + wait_queue_head_t wait_queue; + u32 bac_drive_conf; + u32 bac_walk_conf; + u32 bac_smd_conf; + u32 bac_bike_conf; + u32 bac_run_conf; + u32 bac_still_conf; + u32 power_on_data; + u8 fifo_data_store[HARDWARE_FIFO_SIZE + LEFT_OVER_BYTES]; + u8 int_en; + u8 int_en_2; + u8 gesture_int_count; + u8 smplrt_div; +}; + +/** + * struct inv_mpu_slave - MPU slave structure. + * @st_upper: compass self test upper limit. + * @st_lower: compass self test lower limit. + * @scale: compass scale. + * @rate_scale: decide how fast a compass can read. + * @min_read_time: minimum time between each reading. + * @self_test: self test method of the slave. + * @set_scale: set scale of slave + * @get_scale: read scale back of the slave. + * @suspend: suspend operation. + * @resume: resume operation. + * @setup: setup chip. initialization. + * @combine_data: combine raw data into meaningful data. + * @read_data: read external sensor and output + * @get_mode: get current chip mode. + * @set_lpf: set low pass filter. + * @set_fs: set full scale + * @prev_ts: last time it is read. + */ +struct inv_mpu_slave { + const short *st_upper; + const short *st_lower; + int scale; + int rate_scale; + int min_read_time; + int (*self_test) (struct inv_mpu_state *); + int (*set_scale) (struct inv_mpu_state *, int scale); + int (*get_scale) (struct inv_mpu_state *, int *val); + int (*suspend) (struct inv_mpu_state *); + int (*resume) (struct inv_mpu_state *); + int (*setup) (struct inv_mpu_state *); + int (*combine_data) (u8 *in, short *out); + int (*read_data) (struct inv_mpu_state *, short *out); + int (*get_mode) (void); + int (*set_lpf) (struct inv_mpu_state *, int rate); + int (*set_fs) (struct inv_mpu_state *, int fs); + u64 prev_ts; +}; + +/* scan element definition */ +enum inv_mpu_scan { + INV_MPU_SCAN_TIMESTAMP, +}; + +/* IIO attribute address */ +enum MPU_IIO_ATTR_ADDR { + ATTR_DMP_GYRO_X_DMP_BIAS, + ATTR_DMP_GYRO_Y_DMP_BIAS, + ATTR_DMP_GYRO_Z_DMP_BIAS, + ATTR_DMP_GYRO_CAL_ENABLE, + ATTR_DMP_ACCEL_X_DMP_BIAS, + ATTR_DMP_ACCEL_Y_DMP_BIAS, + ATTR_DMP_ACCEL_Z_DMP_BIAS, + ATTR_DMP_MAGN_X_DMP_BIAS, + ATTR_DMP_MAGN_Y_DMP_BIAS, + ATTR_DMP_MAGN_Z_DMP_BIAS, + ATTR_DMP_MAGN_ACCURACY, + ATTR_GYRO_X_OFFSET, + ATTR_GYRO_Y_OFFSET, + ATTR_GYRO_Z_OFFSET, + ATTR_ACCEL_X_OFFSET, + ATTR_ACCEL_Y_OFFSET, + ATTR_ACCEL_Z_OFFSET, + ATTR_DMP_SC_AUTH, + ATTR_DMP_EIS_AUTH, + ATTR_DMP_ACCEL_CAL_ENABLE, + ATTR_DMP_PED_INT_ON, + ATTR_DMP_PED_STEP_THRESH, + ATTR_DMP_PED_INT_THRESH, + ATTR_DMP_PED_ON, + ATTR_DMP_SMD_ENABLE, + ATTR_DMP_TILT_ENABLE, + ATTR_DMP_PICK_UP_ENABLE, + ATTR_DMP_EIS_ENABLE, + ATTR_DMP_PEDOMETER_STEPS, + ATTR_DMP_PEDOMETER_TIME, + ATTR_DMP_PEDOMETER_COUNTER, + ATTR_DMP_LOW_POWER_GYRO_ON, + ATTR_DMP_LP_EN_OFF, + ATTR_DMP_CLK_SEL, + ATTR_DMP_DEBUG_MEM_READ, + ATTR_DMP_DEBUG_MEM_WRITE, + ATTR_DEBUG_REG_WRITE, + ATTR_DEBUG_WRITE_CFG, + ATTR_DEBUG_REG_ADDR, + ATTR_WOM_THLD, + /* *****above this line, are DMP features, power needs on/off */ + /* *****below this line, are DMP features, no power needed */ + ATTR_IN_POWER_ON, + ATTR_DMP_ON, + ATTR_DMP_EVENT_INT_ON, + ATTR_DMP_STEP_COUNTER_ON, + ATTR_DMP_STEP_COUNTER_WAKE_ON, + ATTR_DMP_BATCHMODE_TIMEOUT, + ATTR_DMP_BATCHMODE_WAKE_FIFO_FULL, + ATTR_DMP_STEP_DETECTOR_ON, + ATTR_DMP_STEP_DETECTOR_WAKE_ON, + ATTR_DMP_ACTIVITY_ON, + ATTR_DMP_IN_ANGLVEL_ACCURACY_ENABLE, + ATTR_DMP_IN_ACCEL_ACCURACY_ENABLE, + ATTR_DMP_DEBUG_DETERMINE_ENGINE_ON, + ATTR_DMP_MISC_GYRO_RECALIBRATION, + ATTR_DMP_MISC_ACCEL_RECALIBRATION, + ATTR_DMP_PARAMS_ACCEL_CALIBRATION_THRESHOLD, + ATTR_DMP_PARAMS_ACCEL_CALIBRATION_RATE, + ATTR_GYRO_SCALE, + ATTR_ACCEL_SCALE, + ATTR_COMPASS_SCALE, + ATTR_COMPASS_SENSITIVITY_X, + ATTR_COMPASS_SENSITIVITY_Y, + ATTR_COMPASS_SENSITIVITY_Z, + ATTR_GYRO_ENABLE, + ATTR_ACCEL_ENABLE, + ATTR_COMPASS_ENABLE, + ATTR_FIRMWARE_LOADED, + ATTR_POKE_MODE, + ATTR_ANGLVEL_X_CALIBBIAS, + ATTR_ANGLVEL_Y_CALIBBIAS, + ATTR_ANGLVEL_Z_CALIBBIAS, + ATTR_ACCEL_X_CALIBBIAS, + ATTR_ACCEL_Y_CALIBBIAS, + ATTR_ACCEL_Z_CALIBBIAS, + ATTR_ANGLVEL_X_ST_CALIBBIAS, + ATTR_ANGLVEL_Y_ST_CALIBBIAS, + ATTR_ANGLVEL_Z_ST_CALIBBIAS, + ATTR_ANGLVEL_X_OIS_ST_CALIBBIAS, + ATTR_ANGLVEL_Y_OIS_ST_CALIBBIAS, + ATTR_ANGLVEL_Z_OIS_ST_CALIBBIAS, + ATTR_ACCEL_X_ST_CALIBBIAS, + ATTR_ACCEL_Y_ST_CALIBBIAS, + ATTR_ACCEL_Z_ST_CALIBBIAS, + ATTR_ACCEL_X_OIS_ST_CALIBBIAS, + ATTR_ACCEL_Y_OIS_ST_CALIBBIAS, + ATTR_ACCEL_Z_OIS_ST_CALIBBIAS, + ATTR_GYRO_MATRIX, + ATTR_ACCEL_MATRIX, + ATTR_COMPASS_MATRIX, + ATTR_FSYNC_FRAME_COUNT, + ATTR_SECONDARY_NAME, + ATTR_GYRO_SF, + ATTR_BAC_DRIVE_CONFIDENCE, + ATTR_BAC_WALK_CONFIDENCE, + ATTR_BAC_SMD_CONFIDENCE, + ATTR_BAC_BIKE_CONFIDENCE, + ATTR_BAC_STILL_CONFIDENCE, + ATTR_BAC_RUN_CONFIDENCE, + IN_OIS_ACCEL_FS, + IN_OIS_GYRO_FS, + IN_OIS_ENABLE, +}; + +int inv_mpu_configure_ring(struct iio_dev *indio_dev); +int inv_mpu_probe_trigger(struct iio_dev *indio_dev); +void inv_mpu_unconfigure_ring(struct iio_dev *indio_dev); +void inv_mpu_remove_trigger(struct iio_dev *indio_dev); +#ifdef CONFIG_PM_SLEEP +int inv_mpu_suspend(struct iio_dev *indio_dev); +void inv_mpu_complete(struct iio_dev *indio_dev); +#endif + +int inv_get_pedometer_steps(struct inv_mpu_state *st, int *ped); +int inv_get_pedometer_time(struct inv_mpu_state *st, int *ped); +int inv_read_pedometer_counter(struct inv_mpu_state *st); + +int inv_dmp_read(struct inv_mpu_state *st, int off, int size, u8 *buf); +int inv_firmware_load(struct inv_mpu_state *st); + +int set_inv_enable(struct iio_dev *indio_dev); + +int inv_mpu_setup_compass_slave(struct inv_mpu_state *st); +int inv_mpu_setup_pressure_slave(struct inv_mpu_state *st); +int inv_mpu_setup_als_slave(struct inv_mpu_state *st); +int inv_mpu_initialize(struct inv_mpu_state *st); +int inv_set_accel_sf(struct inv_mpu_state *st); +int inv_set_gyro_sf(struct inv_mpu_state *st); +s64 get_time_ns(void); +int inv_i2c_read_base(struct inv_mpu_state *st, u16 i, u8 r, u16 l, u8 *d); +int inv_i2c_single_write_base(struct inv_mpu_state *st, u16 i, u8 r, u8 d); +int write_be32_to_mem(struct inv_mpu_state *st, u32 data, int addr); +int write_be16_to_mem(struct inv_mpu_state *st, u16 data, int addr); +int read_be32_from_mem(struct inv_mpu_state *st, u32 *o, int addr); +int read_be16_from_mem(struct inv_mpu_state *st, u16 *o, int addr); +u32 inv_get_cntr_diff(u32 curr_counter, u32 prev); +int inv_write_2bytes(struct inv_mpu_state *st, int k, int data); +int inv_set_bank(struct inv_mpu_state *st, u8 bank); +int inv_set_power(struct inv_mpu_state *st, bool power_on); +int inv_switch_power_in_lp(struct inv_mpu_state *st, bool on); +#ifndef CONFIG_INV_MPU_IIO_ICM20608D +int inv_set_accel_config2(struct inv_mpu_state *st, bool cycle_mode); +#endif +int inv_stop_dmp(struct inv_mpu_state *st); +int inv_reset_fifo(struct inv_mpu_state *st, bool turn_off); +int inv_create_dmp_sysfs(struct iio_dev *ind); +int inv_check_chip_type(struct iio_dev *indio_dev, const char *name); +int inv_write_compass_matrix(struct inv_mpu_state *st, int *adj); +irqreturn_t inv_read_fifo(int irq, void *dev_id); +#ifdef TIMER_BASED_BATCHING +void inv_batch_work(struct work_struct *work); +#endif +int inv_flush_batch_data(struct iio_dev *indio_dev, int data); +static inline int mpu_memory_write(struct inv_mpu_state *st, u8 mpu_addr, + u16 mem_addr, u32 len, u8 const *data) +{ + int ret = -1; + + if (st->mem_write) + ret = st->mem_write(st, mpu_addr, mem_addr, len, data); + + return ret; +} +static inline int mpu_memory_read(struct inv_mpu_state *st, u8 mpu_addr, + u16 mem_addr, u32 len, u8 *data) +{ + int ret = -1; + + if (st->mem_read) + ret = st->mem_read(st, mpu_addr, mem_addr, len, data); + + return ret; +} +int inv_read_secondary(struct inv_mpu_state *st, int ind, int addr, + int reg, int len); +int inv_write_secondary(struct inv_mpu_state *st, int ind, int addr, + int reg, int v); +int inv_execute_write_secondary(struct inv_mpu_state *st, int ind, int addr, + int reg, int v); +int inv_execute_read_secondary(struct inv_mpu_state *st, int ind, int addr, + int reg, int len, u8 *d); + +int inv_push_16bytes_buffer(struct inv_mpu_state *st, u16 hdr, + u64 t, int *q, s16 accur); +int inv_push_gyro_data(struct inv_mpu_state *st, s16 *raw, s32 *calib, u64 t); +int inv_push_8bytes_buffer(struct inv_mpu_state *st, u16 hdr, u64 t, s16 *d); +int inv_push_8bytes_kf(struct inv_mpu_state *st, u16 hdr, u64 t, s16 *d); + +void inv_push_step_indicator(struct inv_mpu_state *st, u64 t); +int inv_send_steps(struct inv_mpu_state *st, int step, u64 t); +int inv_push_marker_to_buffer(struct inv_mpu_state *st, u16 hdr, int data); + +int inv_check_sensor_on(struct inv_mpu_state *st); +int inv_write_cntl(struct inv_mpu_state *st, u16 wd, bool en, int cntl); + +int inv_get_packet_size(struct inv_mpu_state *st, u16 hdr, + u32 *pk_size, u8 *dptr); +int inv_parse_packet(struct inv_mpu_state *st, u16 hdr, u8 *dptr); +int inv_pre_parse_packet(struct inv_mpu_state *st, u16 hdr, u8 *dptr); +int inv_process_dmp_data(struct inv_mpu_state *st); + +int be32_to_int(u8 *d); +void inv_convert_and_push_16bytes(struct inv_mpu_state *st, u16 hdr, + u8 *d, u64 t, s8 *m); +void inv_convert_and_push_8bytes(struct inv_mpu_state *st, u16 hdr, + u8 *d, u64 t, s8 *m); +int inv_get_dmp_ts(struct inv_mpu_state *st, int i); +int inv_process_step_det(struct inv_mpu_state *st, u8 *dptr); +int inv_process_eis(struct inv_mpu_state *st, u16 delay); +int inv_rate_convert(struct inv_mpu_state *st, int ind, int data); + +int inv_setup_dmp_firmware(struct inv_mpu_state *st); +/* used to print i2c data using pr_debug */ +char *wr_pr_debug_begin(u8 const *data, u32 len, char *string); +char *wr_pr_debug_end(char *string); + +int inv_hw_self_test(struct inv_mpu_state *st); +int inv_q30_mult(int a, int b); +#ifdef ACCEL_BIAS_TEST +int inv_get_3axis_average(s16 src[], s16 dst[], s16 reset); +#endif + +static inline int inv_plat_single_write(struct inv_mpu_state *st, + u8 reg, u8 data) +{ + int ret = -1; + + if (st->write) + ret = st->write(st, reg, data); + + return ret; +} +static inline int inv_plat_read(struct inv_mpu_state *st, u8 reg, + int len, u8 *data) +{ + int ret = -1; + + if (st->read) + ret = st->read(st, reg, len, data); + + return ret; +} +irqreturn_t inv_read_fifo(int , void *); + +int inv_stop_interrupt(struct inv_mpu_state *st); +int inv_reenable_interrupt(struct inv_mpu_state *st); + +int inv_enable_pedometer_interrupt(struct inv_mpu_state *st, bool en); +int inv_dataout_control1(struct inv_mpu_state *st, u16 cntl1); +int inv_dataout_control2(struct inv_mpu_state *st, u16 cntl2); +int inv_motion_interrupt_control(struct inv_mpu_state *st, + u16 motion_event_cntl); + +int inv_bound_timestamp(struct inv_mpu_state *st); +int inv_update_dmp_ts(struct inv_mpu_state *st, int ind); +int inv_get_last_run_time_non_dmp_record_mode(struct inv_mpu_state *st); + +#define mem_w(a, b, c) mpu_memory_write(st, st->i2c_addr, a, b, c) +#define mem_r(a, b, c) mpu_memory_read(st, st->i2c_addr, a, b, c) + +#endif /* #ifndef _INV_MPU_IIO_H_ */ diff --git a/drivers/iio/imu/inv_mpu/inv_mpu_ring.c b/drivers/iio/imu/inv_mpu/inv_mpu_ring.c new file mode 100644 index 000000000000..3e5bccbea0df --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_mpu_ring.c @@ -0,0 +1,643 @@ +/* +* Copyright (C) 2012-2018 InvenSense, Inc. +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#define pr_fmt(fmt) "inv_mpu: " fmt + +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/sysfs.h> +#include <linux/jiffies.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/poll.h> +#include <linux/math64.h> +#include <linux/miscdevice.h> + +#include "inv_mpu_iio.h" + +static void inv_push_timestamp(struct iio_dev *indio_dev, u64 t) +{ + u8 buf[IIO_BUFFER_BYTES]; + struct inv_mpu_state *st; + + st = iio_priv(indio_dev); + if (st->poke_mode_on) + memcpy(buf, &st->poke_ts, sizeof(t)); + else + memcpy(buf, &t, sizeof(t)); + iio_push_to_buffers(indio_dev, buf); +} + +int inv_push_marker_to_buffer(struct inv_mpu_state *st, u16 hdr, int data) +{ + struct iio_dev *indio_dev = iio_priv_to_dev(st); + u8 buf[IIO_BUFFER_BYTES]; + + memcpy(buf, &hdr, sizeof(hdr)); + memcpy(&buf[4], &data, sizeof(data)); + iio_push_to_buffers(indio_dev, buf); + + return 0; +} +static int inv_calc_precision(struct inv_mpu_state *st) +{ + int diff; + int init; + + if (st->eis.voting_state != 8) + return 0; + diff = abs(st->eis.fsync_delay_s[1] - st->eis.fsync_delay_s[0]); + init = 0; + if (diff) + init = st->sensor[SENSOR_GYRO].dur / diff; + + if (abs(init - NSEC_PER_USEC) < (NSEC_PER_USEC >> 3)) + st->eis.count_precision = init; + else + st->eis.voting_state = 0; + + pr_debug("dur= %d prc= %d\n", st->sensor[SENSOR_GYRO].dur, + st->eis.count_precision); + + return 0; +} + +static s64 calc_frame_ave(struct inv_mpu_state *st, int delay) +{ + s64 ts; + + ts = st->eis.current_timestamp - delay; +#if defined(CONFIG_INV_MPU_IIO_ICM20648) | defined(CONFIG_INV_MPU_IIO_ICM20690) + ts -= st->ts_algo.gyro_ts_shift; +#endif + pr_debug("shift= %d ts = %lld\n", st->ts_algo.gyro_ts_shift, ts); + + return ts; +} + +static void inv_push_eis_ring(struct inv_mpu_state *st, int *q, bool sync, + s64 t) +{ + struct iio_dev *indio_dev = iio_priv_to_dev(st); + struct inv_eis *eis = &st->eis; + u8 buf[IIO_BUFFER_BYTES]; + int tmp, ii; + + buf[0] = (EIS_GYRO_HDR & 0xff); + buf[1] = (EIS_GYRO_HDR >> 8); + memcpy(buf + 4, &q[0], sizeof(q[0])); + iio_push_to_buffers(indio_dev, buf); + for (ii = 0; ii < 2; ii++) + memcpy(buf + 4 * ii, &q[ii + 1], sizeof(q[ii])); + iio_push_to_buffers(indio_dev, buf); + tmp = eis->frame_count; + if (sync) + tmp |= 0x80000000; + memcpy(buf, &tmp, sizeof(tmp)); + iio_push_to_buffers(indio_dev, buf); + inv_push_timestamp(indio_dev, t); +} +static int inv_do_interpolation_gyro(struct inv_mpu_state *st, int *prev, + s64 prev_t, int *curr, s64 curr_t, s64 t, bool trigger) +{ + int i; + int out[3]; +#if defined(CONFIG_INV_MPU_IIO_ICM20648) | defined(CONFIG_INV_MPU_IIO_ICM20690) + prev_t -= st->ts_algo.gyro_ts_shift; + prev_t += MPU_4X_TS_GYRO_SHIFT; + curr_t -= st->ts_algo.gyro_ts_shift; + curr_t += MPU_4X_TS_GYRO_SHIFT; +#endif + if ((t > prev_t) && (t < curr_t)) { + for (i = 0; i < 3; i++) + out[i] = (int)div_s64((s64)(curr[i] - prev[i]) * + (s64)(t - prev_t), curr_t - prev_t) + prev[i]; + } else if (t < prev_t) { + for (i = 0; i < 3; i++) + out[i] = prev[i]; + } else { + for (i = 0; i < 3; i++) + out[i] = curr[i]; + } + pr_debug("prev= %lld t = %lld curr= %lld\n", prev_t, t, curr_t); + pr_debug("prev = %d, %d, %d\n", prev[0], prev[1], prev[2]); + pr_debug("curr = %d, %d, %d\n", curr[0], curr[1], curr[2]); + pr_debug("out = %d, %d, %d\n", out[0], out[1], out[2]); + inv_push_eis_ring(st, out, trigger, t); + + return 0; +} +#if defined(CONFIG_INV_MPU_IIO_ICM20648) | defined(CONFIG_INV_MPU_IIO_ICM20690) +static void inv_handle_triggered_eis(struct inv_mpu_state *st) +{ + struct inv_eis *eis = &st->eis; + int delay; + + if (st->eis.eis_frame) { + inv_calc_precision(st); + delay = ((int)st->eis.fsync_delay) * st->eis.count_precision; + eis->fsync_timestamp = calc_frame_ave(st, delay); + inv_do_interpolation_gyro(st, + st->eis.prev_gyro, st->eis.prev_timestamp, + st->eis.current_gyro, st->eis.current_timestamp, + eis->fsync_timestamp, true); + pr_debug("fsync=%lld, curr=%lld, delay=%d\n", + eis->fsync_timestamp, eis->current_timestamp, delay); + inv_push_eis_ring(st, st->eis.current_gyro, false, + st->eis.current_timestamp - st->ts_algo.gyro_ts_shift + + MPU_4X_TS_GYRO_SHIFT); + eis->last_fsync_timestamp = eis->fsync_timestamp; + } else { + pr_debug("cur= %lld\n", st->eis.current_timestamp); + inv_push_eis_ring(st, st->eis.current_gyro, false, + st->eis.current_timestamp - st->ts_algo.gyro_ts_shift + + MPU_4X_TS_GYRO_SHIFT); + } +} +#else +static void inv_handle_triggered_eis(struct inv_mpu_state *st) +{ + struct inv_eis *eis = &st->eis; + int delay; + + if ((st->eis.eis_frame && (st->eis.fsync_delay != 5)) || + (st->eis.eis_frame && (st->eis.fsync_delay == 5) && + (!st->eis.current_sync)) + ) { + inv_calc_precision(st); + delay = ((int)st->eis.fsync_delay) * st->eis.count_precision; + eis->fsync_timestamp = calc_frame_ave(st, delay); + inv_do_interpolation_gyro(st, + st->eis.prev_gyro, st->eis.prev_timestamp, + st->eis.current_gyro, st->eis.current_timestamp, + eis->fsync_timestamp, true); + pr_debug("fsync=%lld, curr=%lld, delay=%d\n", + eis->fsync_timestamp, eis->current_timestamp, delay); + inv_push_eis_ring(st, st->eis.current_gyro, false, + st->eis.current_timestamp); + eis->last_fsync_timestamp = eis->fsync_timestamp; + st->eis.eis_frame = false; + } else { + st->eis.current_sync = false; + pr_debug("cur= %lld\n", st->eis.current_timestamp); + inv_push_eis_ring(st, st->eis.current_gyro, false, + st->eis.current_timestamp); + } +} +#endif +static void inv_push_eis_buffer(struct inv_mpu_state *st, u64 t, int *q) +{ + int ii; + + if (st->eis.eis_triggered) { + for (ii = 0; ii < 3; ii++) + st->eis.prev_gyro[ii] = st->eis.current_gyro[ii]; + st->eis.prev_timestamp = st->eis.current_timestamp; + + for (ii = 0; ii < 3; ii++) + st->eis.current_gyro[ii] = q[ii]; + st->eis.current_timestamp = t; + inv_handle_triggered_eis(st); + } else { + for (ii = 0; ii < 3; ii++) + st->eis.current_gyro[ii] = q[ii]; + st->eis.current_timestamp = t; + } +} +static int inv_push_16bytes_final(struct inv_mpu_state *st, int j, + s32 *q, u64 t, s16 accur) +{ + struct iio_dev *indio_dev = iio_priv_to_dev(st); + u8 buf[IIO_BUFFER_BYTES]; + int ii; + + memcpy(buf, &st->sensor_l[j].header, sizeof(st->sensor_l[j].header)); + memcpy(buf + 2, &accur, sizeof(accur)); + memcpy(buf + 4, &q[0], sizeof(q[0])); + iio_push_to_buffers(indio_dev, buf); + for (ii = 0; ii < 2; ii++) + memcpy(buf + 4 * ii, &q[ii + 1], sizeof(q[ii])); + iio_push_to_buffers(indio_dev, buf); + inv_push_timestamp(indio_dev, t); + st->sensor_l[j].counter = 0; + if (st->sensor_l[j].wake_on) + st->wake_sensor_received = true; + + return 0; +} +int inv_push_16bytes_buffer(struct inv_mpu_state *st, u16 sensor, + u64 t, int *q, s16 accur) +{ + int j; + + for (j = 0; j < SENSOR_L_NUM_MAX; j++) { + if (st->sensor_l[j].on && (st->sensor_l[j].base == sensor)) { + st->sensor_l[j].counter++; + if ((st->sensor_l[j].div != 0xffff) && + (st->sensor_l[j].counter >= + st->sensor_l[j].div)) { + pr_debug( + "Sensor_l = %d sensor = %d header [%04X] div [%d] ts [%lld] %d %d %d\n", + j, sensor, + st->sensor_l[j].header, + st->sensor_l[j].div, + t, q[0], q[1], q[2]); + inv_push_16bytes_final(st, j, q, t, accur); + } + } + } + return 0; +} + +void inv_convert_and_push_16bytes(struct inv_mpu_state *st, u16 hdr, + u8 *d, u64 t, s8 *m) +{ + int i, j; + s32 in[3], out[3]; + + for (i = 0; i < 3; i++) + in[i] = be32_to_int(d + i * 4); + /* multiply with orientation matrix can be optimized like this */ + for (i = 0; i < 3; i++) + for (j = 0; j < 3; j++) + if (m[i * 3 + j]) + out[i] = in[j] * m[i * 3 + j]; + + inv_push_16bytes_buffer(st, hdr, t, out, 0); +} + +void inv_convert_and_push_8bytes(struct inv_mpu_state *st, u16 hdr, + u8 *d, u64 t, s8 *m) +{ + int i, j; + s16 in[3], out[3]; + + for (i = 0; i < 3; i++) + in[i] = be16_to_cpup((__be16 *) (d + i * 2)); + + /* multiply with orientation matrix can be optimized like this */ + for (i = 0; i < 3; i++) + for (j = 0; j < 3; j++) + if (m[i * 3 + j]) + out[i] = in[j] * m[i * 3 + j]; + + inv_push_8bytes_buffer(st, hdr, t, out); +} + +int inv_push_special_8bytes_buffer(struct inv_mpu_state *st, + u16 hdr, u64 t, s16 *d) +{ + struct iio_dev *indio_dev = iio_priv_to_dev(st); + u8 buf[IIO_BUFFER_BYTES]; + int j; + + memcpy(buf, &hdr, sizeof(hdr)); + memcpy(&buf[2], &d[0], sizeof(d[0])); + for (j = 0; j < 2; j++) + memcpy(&buf[4 + j * 2], &d[j + 1], sizeof(d[j])); + iio_push_to_buffers(indio_dev, buf); + inv_push_timestamp(indio_dev, t); + + return 0; +} + +static int inv_s16_gyro_push(struct inv_mpu_state *st, int i, s16 *raw, u64 t) +{ + if (st->sensor_l[i].on) { + st->sensor_l[i].counter++; + if ((st->sensor_l[i].div != 0xffff) && + (st->sensor_l[i].counter >= st->sensor_l[i].div)) { + inv_push_special_8bytes_buffer(st, + st->sensor_l[i].header, t, raw); + st->sensor_l[i].counter = 0; + if (st->sensor_l[i].wake_on) + st->wake_sensor_received = true; + } + } + + return 0; +} + +static int inv_s32_gyro_push(struct inv_mpu_state *st, int i, s32 *calib, u64 t) +{ + if (st->sensor_l[i].on) { + st->sensor_l[i].counter++; + if ((st->sensor_l[i].div != 0xffff) && + (st->sensor_l[i].counter >= st->sensor_l[i].div)) { + inv_push_16bytes_final(st, i, calib, t, 0); + st->sensor_l[i].counter = 0; + if (st->sensor_l[i].wake_on) + st->wake_sensor_received = true; + } + } + + return 0; +} + +int inv_push_gyro_data(struct inv_mpu_state *st, s16 *raw, s32 *calib, u64 t) +{ + int gyro_data[] = {SENSOR_L_GYRO, SENSOR_L_GYRO_WAKE}; + int calib_data[] = {SENSOR_L_GYRO_CAL, SENSOR_L_GYRO_CAL_WAKE}; + int i; + + if (st->sensor_l[SENSOR_L_EIS_GYRO].on) + inv_push_eis_buffer(st, t, calib); + + for (i = 0; i < 2; i++) + inv_s16_gyro_push(st, gyro_data[i], raw, t); + for (i = 0; i < 2; i++) + inv_s32_gyro_push(st, calib_data[i], calib, t); + + return 0; +} +int inv_push_8bytes_buffer(struct inv_mpu_state *st, u16 sensor, u64 t, s16 *d) +{ + struct iio_dev *indio_dev = iio_priv_to_dev(st); + u8 buf[IIO_BUFFER_BYTES]; + int ii, j; + + if ((sensor == STEP_DETECTOR_HDR) || + (sensor == STEP_DETECTOR_WAKE_HDR)) { + memcpy(buf, &sensor, sizeof(sensor)); + memcpy(&buf[2], &d[0], sizeof(d[0])); + for (j = 0; j < 2; j++) + memcpy(&buf[4 + j * 2], &d[j + 1], sizeof(d[j])); + iio_push_to_buffers(indio_dev, buf); + inv_push_timestamp(indio_dev, t); + if (sensor == STEP_DETECTOR_WAKE_HDR) + st->wake_sensor_received = true; + return 0; + } + for (ii = 0; ii < SENSOR_L_NUM_MAX; ii++) { + if (st->sensor_l[ii].on && + (st->sensor_l[ii].base == sensor) && + (st->sensor_l[ii].div != 0xffff)) { + st->sensor_l[ii].counter++; + if (st->sensor_l[ii].counter >= st->sensor_l[ii].div) { + pr_debug( + "Sensor_l = %d sensor = %d header [%04X] div [%d] ts [%lld] %d %d %d\n", + ii, sensor, st->sensor_l[ii].header, + st->sensor_l[ii].div, t, d[0], d[1], d[2]); + + memcpy(buf, &st->sensor_l[ii].header, + sizeof(st->sensor_l[ii].header)); + memcpy(&buf[2], &d[0], sizeof(d[0])); + for (j = 0; j < 2; j++) + memcpy(&buf[4 + j * 2], &d[j + 1], + sizeof(d[j])); + + iio_push_to_buffers(indio_dev, buf); + inv_push_timestamp(indio_dev, t); + st->sensor_l[ii].counter = 0; + if (st->sensor_l[ii].wake_on) + st->wake_sensor_received = true; + } + } + } + + return 0; +} +#ifdef CONFIG_INV_MPU_IIO_ICM20648 +/* Implemented activity to string function for BAC test */ +#define TILT_DETECTED 0x1000 +#define NONE 0x00 +#define DRIVE 0x01 +#define WALK 0x02 +#define RUN 0x04 +#define BIKE 0x08 +#define TILT 0x10 +#define STILL 0x20 +#define DRIVE_WALK (DRIVE | WALK) +#define DRIVE_RUN (DRIVE | RUN) + +char *act_string(s16 data) +{ + data &= (~TILT); + switch (data) { + case NONE: + return "None"; + case DRIVE: + return "Drive"; + case WALK: + return "Walk"; + case RUN: + return "Run"; + case BIKE: + return "Bike"; + case STILL: + return "Still"; + case DRIVE_WALK: + return "drive and walk"; + case DRIVE_RUN: + return "drive and run"; + default: + return "Unknown"; + } + return "Unknown"; +} + +char *inv_tilt_check(s16 data) +{ + if (data & TILT) + return "Tilt"; + else + return "None"; +} + +int inv_push_8bytes_kf(struct inv_mpu_state *st, u16 hdr, u64 t, s16 *d) +{ + struct iio_dev *indio_dev = iio_priv_to_dev(st); + u8 buf[IIO_BUFFER_BYTES]; + int i; + + if (st->chip_config.activity_on) { + memcpy(buf, &hdr, sizeof(hdr)); + for (i = 0; i < 3; i++) + memcpy(&buf[2 + i * 2], &d[i], sizeof(d[i])); + + kfifo_in(&st->kf, buf, IIO_BUFFER_BYTES); + memcpy(buf, &t, sizeof(t)); + kfifo_in(&st->kf, buf, IIO_BUFFER_BYTES); + st->activity_size += IIO_BUFFER_BYTES * 2; + } + if (st->chip_config.tilt_enable) { + pr_debug("d[0] = %04X, [%X : %s] to [%X : %s]", + d[0], d[0] & 0x00FF, + inv_tilt_check(d[0] & 0x00FF), + (d[0] & 0xFF00) >> 8, inv_tilt_check((d[0] & 0xFF00) >> 8)); + sysfs_notify(&indio_dev->dev.kobj, NULL, "poll_tilt"); + } + + pr_debug("d[0] = %04X, [%X : %s] to [%X : %s]", d[0], d[0] & 0x00FF, + act_string(d[0] & 0x00FF), + (d[0] & 0xFF00) >> 8, act_string((d[0] & 0xFF00) >> 8)); + + read_be32_from_mem(st, &st->bac_drive_conf, BAC_DRIVE_CONFIDENCE); + read_be32_from_mem(st, &st->bac_walk_conf, BAC_WALK_CONFIDENCE); + read_be32_from_mem(st, &st->bac_smd_conf, BAC_SMD_CONFIDENCE); + read_be32_from_mem(st, &st->bac_bike_conf, BAC_BIKE_CONFIDENCE); + read_be32_from_mem(st, &st->bac_still_conf, BAC_STILL_CONFIDENCE); + read_be32_from_mem(st, &st->bac_run_conf, BAC_RUN_CONFIDENCE); + + return 0; +} +#endif + +int inv_send_steps(struct inv_mpu_state *st, int step, u64 ts) +{ + s16 s[3]; + + s[0] = 0; + s[1] = (s16) (step & 0xffff); + s[2] = (s16) ((step >> 16) & 0xffff); + if (st->step_counter_l_on) + inv_push_special_8bytes_buffer(st, STEP_COUNTER_HDR, ts, s); + if (st->step_counter_wake_l_on) { + inv_push_special_8bytes_buffer(st, STEP_COUNTER_WAKE_HDR, + ts, s); + st->wake_sensor_received = true; + } + return 0; +} + +void inv_push_step_indicator(struct inv_mpu_state *st, u64 t) +{ + s16 sen[3]; +#define STEP_INDICATOR_HEADER 0x0001 + + sen[0] = 0; + sen[1] = 0; + sen[2] = 0; + inv_push_8bytes_buffer(st, STEP_INDICATOR_HEADER, t, sen); +} + +/* + * inv_irq_handler() - Cache a timestamp at each data ready interrupt. + */ +static irqreturn_t inv_irq_handler(int irq, void *dev_id) +{ + return IRQ_WAKE_THREAD; +} + +#ifdef TIMER_BASED_BATCHING +static enum hrtimer_restart inv_batch_timer_handler(struct hrtimer *timer) +{ + struct inv_mpu_state *st = + container_of(timer, struct inv_mpu_state, hr_batch_timer); + + if (st->chip_config.gyro_enable || st->chip_config.accel_enable) { + hrtimer_forward_now(&st->hr_batch_timer, + ns_to_ktime(st->batch_timeout)); + schedule_work(&st->batch_work); + return HRTIMER_RESTART; + } + st->is_batch_timer_running = 0; + return HRTIMER_NORESTART; +} +#endif + +void inv_mpu_unconfigure_ring(struct iio_dev *indio_dev) +{ + struct inv_mpu_state *st = iio_priv(indio_dev); +#ifdef KERNEL_VERSION_4_X + devm_free_irq(st->dev, st->irq, st); + devm_iio_kfifo_free(st->dev, indio_dev->buffer); +#else + free_irq(st->irq, st); + iio_kfifo_free(indio_dev->buffer); +#endif +}; +EXPORT_SYMBOL_GPL(inv_mpu_unconfigure_ring); + +#ifndef KERNEL_VERSION_4_X +static int inv_predisable(struct iio_dev *indio_dev) +{ + return 0; +} + +static int inv_preenable(struct iio_dev *indio_dev) +{ + return 0; +} + +static const struct iio_buffer_setup_ops inv_mpu_ring_setup_ops = { + .preenable = &inv_preenable, + .predisable = &inv_predisable, +}; +#endif + +int inv_mpu_configure_ring(struct iio_dev *indio_dev) +{ + int ret; + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_buffer *ring; + +#ifdef TIMER_BASED_BATCHING + /* configure hrtimer */ + hrtimer_init(&st->hr_batch_timer, CLOCK_BOOTTIME, HRTIMER_MODE_REL); + st->hr_batch_timer.function = inv_batch_timer_handler; + INIT_WORK(&st->batch_work, inv_batch_work); +#endif +#ifdef KERNEL_VERSION_4_X + ring = devm_iio_kfifo_allocate(st->dev); + if (!ring) + return -ENOMEM; + ring->scan_timestamp = true; + iio_device_attach_buffer(indio_dev, ring); + ret = devm_request_threaded_irq(st->dev, + st->irq, + inv_irq_handler, + inv_read_fifo, + IRQF_TRIGGER_RISING | IRQF_SHARED, + "inv_irq", + st); + if (ret) { + devm_iio_kfifo_free(st->dev, ring); + return ret; + } + + // this mode does not use ops + indio_dev->modes = INDIO_ALL_BUFFER_MODES; + + return ret; +#else + ring = iio_kfifo_allocate(indio_dev); + if (!ring) + return -ENOMEM; + indio_dev->buffer = ring; + /* setup ring buffer */ + ring->scan_timestamp = true; + indio_dev->setup_ops = &inv_mpu_ring_setup_ops; + ret = request_threaded_irq(st->irq, + inv_irq_handler, + inv_read_fifo, + IRQF_TRIGGER_RISING | IRQF_SHARED, + "inv_irq", + st); + if (ret) + goto error_iio_sw_rb_free; + + indio_dev->modes |= INDIO_BUFFER_HARDWARE; + + return 0; +error_iio_sw_rb_free: + iio_kfifo_free(indio_dev->buffer); + + return ret; +#endif +} +EXPORT_SYMBOL_GPL(inv_mpu_configure_ring); diff --git a/drivers/iio/imu/inv_mpu/inv_mpu_spi.c b/drivers/iio/imu/inv_mpu/inv_mpu_spi.c new file mode 100644 index 000000000000..fb916788a9df --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_mpu_spi.c @@ -0,0 +1,410 @@ +/* +* Copyright (C) 2012-2018 InvenSense, Inc. +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#define pr_fmt(fmt) "inv_mpu: " fmt + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/sysfs.h> +#include <linux/jiffies.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/poll.h> +#include <linux/miscdevice.h> +#include <linux/spinlock.h> + +#include "inv_mpu_iio.h" +#include "inv_mpu_dts.h" + +#define INV_SPI_READ 0x80 + +static int inv_spi_single_write(struct inv_mpu_state *st, u8 reg, u8 data) +{ + struct spi_message msg; + int res; + u8 d[2]; + struct spi_transfer xfers = { + .tx_buf = d, + .bits_per_word = 8, + .len = 2, + }; + + pr_debug("reg_write: reg=0x%x data=0x%x\n", reg, data); + d[0] = reg; + d[1] = data; + spi_message_init(&msg); + spi_message_add_tail(&xfers, &msg); + res = spi_sync(to_spi_device(st->dev), &msg); + + return res; +} + +static int inv_spi_read(struct inv_mpu_state *st, u8 reg, int len, u8 *data) +{ + struct spi_message msg; + int res; + u8 d[1]; + struct spi_transfer xfers[] = { + { + .tx_buf = d, + .bits_per_word = 8, + .len = 1, + }, + { + .rx_buf = data, + .bits_per_word = 8, + .len = len, + } + }; + + if (!data) + return -EINVAL; + + d[0] = (reg | INV_SPI_READ); + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + res = spi_sync(to_spi_device(st->dev), &msg); + + if (len ==1) + pr_debug("reg_read: reg=0x%x length=%d data=0x%x\n", + reg, len, data[0]); + else + pr_debug("reg_read: reg=0x%x length=%d d0=0x%x d1=0x%x\n", + reg, len, data[0], data[1]); + + return res; + +} + +static int inv_spi_mem_write(struct inv_mpu_state *st, u8 mpu_addr, u16 mem_addr, + u32 len, u8 const *data) +{ + struct spi_message msg; + u8 buf[258]; + int res; + + struct spi_transfer xfers = { + .tx_buf = buf, + .bits_per_word = 8, + .len = len + 1, + }; + + if (!data || !st) + return -EINVAL; + + if (len > (sizeof(buf) - 1)) + return -ENOMEM; + + inv_plat_single_write(st, REG_MEM_BANK_SEL, mem_addr >> 8); + inv_plat_single_write(st, REG_MEM_START_ADDR, mem_addr & 0xFF); + + buf[0] = REG_MEM_R_W; + memcpy(buf + 1, data, len); + spi_message_init(&msg); + spi_message_add_tail(&xfers, &msg); + res = spi_sync(to_spi_device(st->dev), &msg); + + return res; +} + +static int inv_spi_mem_read(struct inv_mpu_state *st, u8 mpu_addr, u16 mem_addr, + u32 len, u8 *data) +{ + int res; + + if (!data || !st) + return -EINVAL; + + if (len > 256) + return -EINVAL; + + res = inv_plat_single_write(st, REG_MEM_BANK_SEL, mem_addr >> 8); + res = inv_plat_single_write(st, REG_MEM_START_ADDR, mem_addr & 0xFF); + res = inv_plat_read(st, REG_MEM_R_W, len, data); + + return res; +} + +/* + * inv_mpu_probe() - probe function. + */ +static int inv_mpu_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct inv_mpu_state *st; + struct iio_dev *indio_dev; + int result; + +#ifdef KERNEL_VERSION_4_X + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) { + pr_err("memory allocation failed\n"); + result = -ENOMEM; + goto out_no_free; + } +#else + indio_dev = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + pr_err("memory allocation failed\n"); + result = -ENOMEM; + goto out_no_free; + } +#endif + st = iio_priv(indio_dev); + st->write = inv_spi_single_write; + st->read = inv_spi_read; + st->mem_write = inv_spi_mem_write; + st->mem_read = inv_spi_mem_read; + st->dev = &spi->dev; + st->irq = spi->irq; +#if !defined(CONFIG_INV_MPU_IIO_ICM20602) \ + && !defined(CONFIG_INV_MPU_IIO_IAM20680) + st->i2c_dis = BIT_I2C_IF_DIS; +#endif + st->bus_type = BUS_SPI; + spi_set_drvdata(spi, indio_dev); + indio_dev->dev.parent = &spi->dev; + indio_dev->name = id->name; + +#ifdef CONFIG_OF + result = invensense_mpu_parse_dt(st->dev, &st->plat_data); + if (result) +# ifdef KERNEL_VERSION_4_X + return -ENODEV; +# else + goto out_free; +# endif + /* Power on device */ + if (st->plat_data.power_on) { + result = st->plat_data.power_on(&st->plat_data); + if (result < 0) { + dev_err(st->dev, "power_on failed: %d\n", result); +# ifdef KERNEL_VERSION_4_X + return -ENODEV; +# else + goto out_free; +# endif + } + pr_info("%s: power on here.\n", __func__); + } + pr_info("%s: power on.\n", __func__); + + msleep(100); +#else + if (dev_get_platdata(st->dev) == NULL) +# ifdef KERNEL_VERSION_4_X + return -ENODEV; +# else + goto out_free; +# endif + st->plat_data = *(struct mpu_platform_data *)dev_get_platdata(st->dev); +#endif + + /* power is turned on inside check chip type */ + result = inv_check_chip_type(indio_dev, id->name); + if (result) +#ifdef KERNEL_VERSION_4_X + return -ENODEV; +#else + goto out_free; +#endif + + result = inv_mpu_configure_ring(indio_dev); + if (result) { + pr_err("configure ring buffer fail\n"); + goto out_free; + } +#ifdef KERNEL_VERSION_4_X + result = devm_iio_device_register(st->dev, indio_dev); + if (result) { + pr_err("IIO device register fail\n"); + goto out_unreg_ring; + } +#else + result = iio_buffer_register(indio_dev, indio_dev->channels, + indio_dev->num_channels); + if (result) { + pr_err("ring buffer register fail\n"); + goto out_unreg_ring; + } + + result = iio_device_register(indio_dev); + if (result) { + pr_err("IIO device register fail\n"); + goto out_remove_ring; + } +#endif + + result = inv_create_dmp_sysfs(indio_dev); + if (result) { + pr_err("create dmp sysfs failed\n"); + goto out_unreg_iio; + } + init_waitqueue_head(&st->wait_queue); + st->resume_state = true; +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_init(&st->wake_lock, WAKE_LOCK_SUSPEND, "inv_mpu"); +#else + wakeup_source_init(&st->wake_lock, "inv_mpu"); +#endif + dev_info(st->dev, "%s ma-kernel-%s is ready to go!\n", + indio_dev->name, INVENSENSE_DRIVER_VERSION); + +#ifdef SENSOR_DATA_FROM_REGISTERS + pr_info("Data read from registers\n"); +#else + pr_info("Data read from FIFO\n"); +#endif +#ifdef TIMER_BASED_BATCHING + pr_info("Timer based batching\n"); +#endif + + return 0; +#ifdef KERNEL_VERSION_4_X +out_unreg_iio: + devm_iio_device_unregister(st->dev, indio_dev); +out_unreg_ring: + inv_mpu_unconfigure_ring(indio_dev); +out_free: + devm_iio_device_free(st->dev, indio_dev); +out_no_free: +#else +out_unreg_iio: + iio_device_unregister(indio_dev); +out_remove_ring: + iio_buffer_unregister(indio_dev); +out_unreg_ring: + inv_mpu_unconfigure_ring(indio_dev); +out_free: + iio_device_free(indio_dev); +out_no_free: +#endif + dev_err(st->dev, "%s failed %d\n", __func__, result); + + return -EIO; +} + +static void inv_mpu_shutdown(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct inv_mpu_state *st = iio_priv(indio_dev); + int result; + + mutex_lock(&indio_dev->mlock); + inv_switch_power_in_lp(st, true); + dev_dbg(st->dev, "Shutting down %s...\n", st->hw->name); + + /* reset to make sure previous state are not there */ + result = inv_plat_single_write(st, REG_PWR_MGMT_1, BIT_H_RESET); + if (result) + dev_err(st->dev, "Failed to reset %s\n", + st->hw->name); + msleep(POWER_UP_TIME); + /* turn off power to ensure gyro engine is off */ + result = inv_set_power(st, false); + if (result) + dev_err(st->dev, "Failed to turn off %s\n", + st->hw->name); + inv_switch_power_in_lp(st, false); + mutex_unlock(&indio_dev->mlock); +} + +/* + * inv_mpu_remove() - remove function. + */ +static int inv_mpu_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct inv_mpu_state *st = iio_priv(indio_dev); + +#ifdef KERNEL_VERSION_4_X + devm_iio_device_unregister(st->dev, indio_dev); +#else + iio_device_unregister(indio_dev); + iio_buffer_unregister(indio_dev); +#endif + inv_mpu_unconfigure_ring(indio_dev); +#ifdef KERNEL_VERSION_4_X + devm_iio_device_free(st->dev, indio_dev); +#else + iio_device_free(indio_dev); +#endif + dev_info(st->dev, "inv-mpu-iio module removed.\n"); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int inv_mpu_spi_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = spi_get_drvdata(to_spi_device(dev)); + + return inv_mpu_suspend(indio_dev); +} + +static void inv_mpu_spi_complete(struct device *dev) +{ + struct iio_dev *indio_dev = spi_get_drvdata(to_spi_device(dev)); + + inv_mpu_complete(indio_dev); +} +#endif + +static const struct dev_pm_ops inv_mpu_spi_pmops = { +#ifdef CONFIG_PM_SLEEP + .suspend = inv_mpu_spi_suspend, + .complete = inv_mpu_spi_complete, +#endif +}; + +/* device id table is used to identify what device can be + * supported by this driver + */ +static const struct spi_device_id inv_mpu_id[] = { +#ifdef CONFIG_INV_MPU_IIO_ICM20648 + {"icm20645", ICM20645}, + {"icm10340", ICM10340}, + {"icm20648", ICM20648}, +#else + {"icm20608d", ICM20608D}, + {"icm20690", ICM20690}, + {"icm20602", ICM20602}, + {"iam20680", IAM20680}, +#endif + {} +}; + +MODULE_DEVICE_TABLE(spi, inv_mpu_id); + +static struct spi_driver inv_mpu_driver = { + .probe = inv_mpu_probe, + .remove = inv_mpu_remove, + .shutdown = inv_mpu_shutdown, + .id_table = inv_mpu_id, + .driver = { + .owner = THIS_MODULE, + .name = "inv-mpu-iio-spi", + .pm = &inv_mpu_spi_pmops, + }, +}; +module_spi_driver(inv_mpu_driver); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense SPI device driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/inv_mpu/inv_mpu_timestamp.c b/drivers/iio/imu/inv_mpu/inv_mpu_timestamp.c new file mode 100644 index 000000000000..2cc721b18596 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_mpu_timestamp.c @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2012-2018 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#define pr_fmt(fmt) "inv_mpu: " fmt + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/sysfs.h> +#include <linux/jiffies.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/poll.h> +#include <linux/miscdevice.h> +#include <linux/math64.h> + +#include "inv_mpu_iio.h" + +#define INV_TIME_CALIB_THRESHOLD_1 2 + +#define MIN_DELAY (3 * NSEC_PER_MSEC) +#define JITTER_THRESH ( 1 * NSEC_PER_MSEC) + +int inv_update_dmp_ts(struct inv_mpu_state *st, int ind) +{ + int i; + u32 counter; + u64 ts; + enum INV_ENGINE en_ind; + struct inv_timestamp_algo *ts_algo = &st->ts_algo; + u32 base_time; + u64 cal_period; + + if (st->mode_1k_on) + cal_period = (NSEC_PER_SEC >> 2); + else + cal_period = 2 * NSEC_PER_SEC; + + ts = ts_algo->last_run_time - st->sensor[ind].time_calib; + counter = st->sensor[ind].sample_calib; + en_ind = st->sensor[ind].engine_base; + if (en_ind != ts_algo->clock_base) + return 0; + /* we average over 2 seconds period to do the timestamp calculation */ + if (ts < cal_period) + return 0; + /* this is the first time we do timestamp averaging, return */ + /* after resume from suspend, the clock of linux has up to 1 seconds + drift. We should start from the resume clock instead of using clock + before resume */ + if ((!st->sensor[ind].calib_flag) || ts_algo->resume_flag) { + st->sensor[ind].sample_calib = 0; + st->sensor[ind].time_calib = ts_algo->last_run_time; + st->sensor[ind].calib_flag = 1; + ts_algo->resume_flag = false; + + return 0; + } + /* if the sample number in current FIFO is not zero and between now and + last update time is more than 2 seconds, we do calculation */ + if ((counter > 0) && + (ts_algo->last_run_time - st->eng_info[en_ind].last_update_time > + cal_period)) { + /* duration for each sensor */ + st->sensor[ind].dur = (u32) div_u64(ts, counter); + /* engine duration derived from each sensor */ + if (st->sensor[ind].div) + st->eng_info[en_ind].dur = st->sensor[ind].dur / + st->sensor[ind].div; + else + pr_err("sensor %d divider zero!\n", ind); + /* update base time for each sensor */ + if (st->eng_info[en_ind].divider) { + base_time = (st->eng_info[en_ind].dur / + st->eng_info[en_ind].divider) * + st->eng_info[en_ind].orig_rate; + if (st->mode_1k_on) + st->eng_info[en_ind].base_time_1k = base_time; + else + st->eng_info[en_ind].base_time = base_time; + } else { + pr_err("engine %d divider zero!\n", en_ind); + } + + st->eng_info[en_ind].last_update_time = ts_algo->last_run_time; + /* update all the sensors duration based on the same engine */ + for (i = 0; i < SENSOR_NUM_MAX; i++) { + if (st->sensor[i].on && + (st->sensor[i].engine_base == en_ind)) + st->sensor[i].dur = st->sensor[i].div * + st->eng_info[en_ind].dur; + } + + } + st->sensor[ind].sample_calib = 0; + st->sensor[ind].time_calib = ts_algo->last_run_time; + + return 0; +} +/** + * int inv_get_last_run_time_non_dmp_record_mode(struct inv_mpu_state *st) + * This is the function to get last run time in non dmp and record mode. + * This function will update the last_run_time, which is important parameter + * in overall timestamp algorithm. + * return value: this function returns fifo count value. +*/ +int inv_get_last_run_time_non_dmp_record_mode(struct inv_mpu_state *st) +{ + long long t_pre, t_post, dur; + int fifo_count; +#ifndef SENSOR_DATA_FROM_REGISTERS + int res; + u8 data[2]; +#endif + + t_pre = get_time_ns(); +#ifndef SENSOR_DATA_FROM_REGISTERS + res = inv_plat_read(st, REG_FIFO_COUNT_H, FIFO_COUNT_BYTE, data); + if (res) { + pr_info("read REG_FIFO_COUNT_H failed= %d\n", res); + return 0; + } +#endif + t_post = get_time_ns(); + +#ifdef SENSOR_DATA_FROM_REGISTERS + if (st->fifo_count_mode == BYTE_MODE) + fifo_count = st->batch.pk_size; + else + fifo_count = 1; +#else + fifo_count = be16_to_cpup((__be16 *) (data)); +#endif + pr_debug("fifc=%d\n", fifo_count); + if (!fifo_count) + return 0; + if (st->special_mag_mode && (fifo_count == 2)) { + pr_debug("special trigger\n"); + fifo_count = 1; + } + + /* In non DMP mode, either gyro or accel duration is the duration + for each sample */ + if (st->chip_config.gyro_enable) + dur = st->eng_info[ENGINE_GYRO].dur; + else + dur = st->eng_info[ENGINE_ACCEL].dur; + + if (st->fifo_count_mode == BYTE_MODE) { + fifo_count /= st->batch.pk_size; + } + + /* In record mode, each number in fifo_count is 1 record or 1 sample */ + st->ts_algo.last_run_time += dur * fifo_count; + if (st->ts_algo.last_run_time < t_pre) + st->ts_algo.last_run_time = t_pre; + if (st->ts_algo.last_run_time > t_post) + st->ts_algo.last_run_time = t_post; + + return fifo_count; +} + +int inv_get_dmp_ts(struct inv_mpu_state *st, int i) +{ + u64 current_time; + int expected_lower_duration, expected_upper_duration; + + current_time = get_time_ns(); + + st->sensor[i].ts += st->sensor[i].dur + st->sensor[i].ts_adj; + + if (st->sensor[i].ts < st->sensor[i].previous_ts) + st->sensor[i].ts = st->sensor[i].previous_ts + st->sensor[i].dur; + + //hifi sensor limits ts jitter to +/- 2% + expected_upper_duration = st->eng_info[st->sensor[i].engine_base].divider * 1020000; + expected_lower_duration = st->eng_info[st->sensor[i].engine_base].divider * 980000; +#if defined(CONFIG_INV_MPU_IIO_ICM20602) || defined(CONFIG_INV_MPU_IIO_ICM20690) || defined(CONFIG_INV_MPU_IIO_IAM20680) + if (st->sensor[i].ts < st->sensor[i].previous_ts + expected_lower_duration) + st->sensor[i].ts = st->sensor[i].previous_ts + expected_lower_duration; + if (st->sensor[i].ts > st->sensor[i].previous_ts + expected_upper_duration) + st->sensor[i].ts = st->sensor[i].previous_ts + expected_upper_duration; +#endif + if (st->sensor[i].ts > current_time ) + st->sensor[i].ts = current_time; + + st->sensor[i].previous_ts = st->sensor[i].ts; + + pr_debug("ts=%lld, reset=%lld\n", st->sensor[i].ts, st->ts_algo.reset_ts); + if (st->sensor[i].ts < st->ts_algo.reset_ts) { + pr_debug("less than reset\n"); + st->sensor[i].send = false; + } else { + st->sensor[i].send = true; + } + + if (st->header_count == 1) + inv_update_dmp_ts(st, i); + + return 0; +} + +static void process_sensor_bounding(struct inv_mpu_state *st, int i) +{ + s64 elaps_time, thresh1, thresh2; + struct inv_timestamp_algo *ts_algo = &st->ts_algo; + u32 dur; + + elaps_time = ((u64) (st->sensor[i].dur)) * st->sensor[i].count; + thresh1 = ts_algo->last_run_time - elaps_time; + + dur = max(st->sensor[i].dur, (int)MIN_DELAY); + thresh2 = thresh1 - dur; + if (thresh1 < 0) + thresh1 = 0; + if (thresh2 < 0) + thresh2 = 0; + st->sensor[i].ts_adj = 0; + if ((ts_algo->calib_counter >= INV_TIME_CALIB_THRESHOLD_1) && + (!ts_algo->resume_flag)) { + if (st->sensor[i].ts < thresh2) + st->sensor[i].ts_adj = thresh2 - st->sensor[i].ts; + } else if ((ts_algo->calib_counter >= + INV_TIME_CALIB_THRESHOLD_1) && ts_algo->resume_flag) { + if (st->sensor[i].ts < thresh2) + st->sensor[i].ts = ts_algo->last_run_time - + elaps_time - JITTER_THRESH; + } else { + st->sensor[i].ts = ts_algo->last_run_time - elaps_time - + JITTER_THRESH; + st->sensor[i].previous_ts = st->sensor[i].ts; + } + + if (st->sensor[i].ts > thresh1) + st->sensor[i].ts_adj = thresh1 - st->sensor[i].ts; + pr_debug("cali=%d\n", st->ts_algo.calib_counter); + pr_debug("adj= %lld\n", st->sensor[i].ts_adj); + pr_debug("dur= %d count= %d last= %lld\n", st->sensor[i].dur, + st->sensor[i].count, ts_algo->last_run_time); + if (st->sensor[i].ts_adj && (st->sensor[i].count > 1)) + st->sensor[i].ts_adj = div_s64(st->sensor[i].ts_adj, + st->sensor[i].count); +} +/* inv_bound_timestamp (struct inv_mpu_state *st) + The purpose this function is to give a generic bound to each + sensor timestamp. The timestamp cannot exceed current time. + The timestamp cannot backwards one sample time either, otherwise, there + would be another sample in between. Using this principle, we can bound + the sensor samples */ +int inv_bound_timestamp(struct inv_mpu_state *st) +{ + int i; + struct inv_timestamp_algo *ts_algo = &st->ts_algo; + + for (i = 0; i < SENSOR_NUM_MAX; i++) { + if (st->sensor[i].on) { + if (st->sensor[i].count) { + process_sensor_bounding(st, i); + } else if (ts_algo->calib_counter < + INV_TIME_CALIB_THRESHOLD_1) { + st->sensor[i].ts = ts_algo->reset_ts; + st->sensor[i].previous_ts = st->sensor[i].ts; + } + } + } + + return 0; +} diff --git a/drivers/iio/imu/inv_mpu/inv_test/Kconfig b/drivers/iio/imu/inv_mpu/inv_test/Kconfig new file mode 100644 index 000000000000..a4dfd95db886 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_test/Kconfig @@ -0,0 +1,13 @@ +# +# Kconfig for Invensense IIO testing hooks +# + +config INV_TESTING + boolean "Invensense IIO testing hooks" + depends on INV_MPU_IIO || INV_AMI306_IIO || INV_YAS530 || INV_HUB_IIO + default n + help + This flag enables display of additional testing information from the + Invensense IIO drivers + It also enables the I2C counters facility to perform IO profiling. + Some additional sysfs entries will appear when this flag is enabled. diff --git a/drivers/iio/imu/inv_mpu/inv_test/Makefile b/drivers/iio/imu/inv_mpu/inv_test/Makefile new file mode 100644 index 000000000000..4f0edd3de901 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_test/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for Invensense IIO testing hooks. +# + +obj-$(CONFIG_INV_TESTING) += inv_counters.o + diff --git a/drivers/iio/imu/inv_mpu/inv_test/inv_counters.c b/drivers/iio/imu/inv_mpu/inv_test/inv_counters.c new file mode 100644 index 000000000000..f60337caeeed --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_test/inv_counters.c @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2012-2017 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/miscdevice.h> +#include <linux/err.h> +#include <linux/sysfs.h> +#include <linux/kdev_t.h> +#include <linux/string.h> +#include <linux/jiffies.h> +#include <linux/spinlock.h> +#include <linux/kernel_stat.h> + +#include "inv_counters.h" + +static int mpu_irq; +static int accel_irq; +static int compass_irq; + +struct inv_counters { + uint32_t i2c_tempreads; + uint32_t i2c_mpureads; + uint32_t i2c_mpuwrites; + uint32_t i2c_accelreads; + uint32_t i2c_accelwrites; + uint32_t i2c_compassreads; + uint32_t i2c_compasswrites; + uint32_t i2c_compassirq; + uint32_t i2c_accelirq; +}; + +static struct inv_counters Counters; + +static ssize_t i2c_counters_show(struct class *cls, + struct class_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, + "%ld.%03ld %u %u %u %u %u %u %u %u %u %u\n", + jiffies / HZ, ((jiffies % HZ) * (1024 / HZ)), + mpu_irq ? kstat_irqs(mpu_irq) : 0, + Counters.i2c_tempreads, + Counters.i2c_mpureads, Counters.i2c_mpuwrites, + accel_irq ? kstat_irqs(accel_irq) : Counters.i2c_accelirq, + Counters.i2c_accelreads, Counters.i2c_accelwrites, + compass_irq ? kstat_irqs(compass_irq) : Counters.i2c_compassirq, + Counters.i2c_compassreads, Counters.i2c_compasswrites); +} + +void inv_iio_counters_set_i2cirq(enum irqtype type, int irq) +{ + switch (type) { + case IRQ_MPU: + mpu_irq = irq; + break; + case IRQ_ACCEL: + accel_irq = irq; + break; + case IRQ_COMPASS: + compass_irq = irq; + break; + } +} +EXPORT_SYMBOL_GPL(inv_iio_counters_set_i2cirq); + +void inv_iio_counters_tempread(int count) +{ + Counters.i2c_tempreads += count; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_tempread); + +void inv_iio_counters_mpuread(int count) +{ + Counters.i2c_mpureads += count; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_mpuread); + +void inv_iio_counters_mpuwrite(int count) +{ + Counters.i2c_mpuwrites += count; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_mpuwrite); + +void inv_iio_counters_accelread(int count) +{ + Counters.i2c_accelreads += count; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_accelread); + +void inv_iio_counters_accelwrite(int count) +{ + Counters.i2c_accelwrites += count; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_accelwrite); + +void inv_iio_counters_compassread(int count) +{ + Counters.i2c_compassreads += count; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_compassread); + +void inv_iio_counters_compasswrite(int count) +{ + Counters.i2c_compasswrites += count; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_compasswrite); + +void inv_iio_counters_compassirq(void) +{ + Counters.i2c_compassirq++; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_compassirq); + +void inv_iio_counters_accelirq(void) +{ + Counters.i2c_accelirq++; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_accelirq); + +static struct class_attribute inv_class_attr[] = { + __ATTR(i2c_counter, S_IRUGO, i2c_counters_show, NULL), + __ATTR_NULL +}; + +static struct class inv_counters_class = { + .name = "inv_counters", + .owner = THIS_MODULE, + .class_attrs = (struct class_attribute *) &inv_class_attr +}; + +static int __init inv_counters_init(void) +{ + memset(&Counters, 0, sizeof(Counters)); + + return class_register(&inv_counters_class); +} + +static void __exit inv_counters_exit(void) +{ + class_unregister(&inv_counters_class); +} + +module_init(inv_counters_init); +module_exit(inv_counters_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("GESL"); +MODULE_DESCRIPTION("inv_counters debug support"); + diff --git a/drivers/iio/imu/inv_mpu/inv_test/inv_counters.h b/drivers/iio/imu/inv_mpu/inv_test/inv_counters.h new file mode 100644 index 000000000000..62f76279e703 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_test/inv_counters.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2012-2017 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _INV_COUNTERS_H_ +#define _INV_COUNTERS_H_ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/sysfs.h> +#include <linux/string.h> +#include <linux/jiffies.h> +#include <linux/spinlock.h> + +#ifdef CONFIG_INV_TESTING + +enum irqtype { + IRQ_MPU, + IRQ_ACCEL, + IRQ_COMPASS +}; + +#define INV_I2C_INC_MPUREAD(x) inv_iio_counters_mpuread(x) +#define INV_I2C_INC_MPUWRITE(x) inv_iio_counters_mpuwrite(x) +#define INV_I2C_INC_ACCELREAD(x) inv_iio_counters_accelread(x) +#define INV_I2C_INC_ACCELWRITE(x) inv_iio_counters_accelwrite(x) +#define INV_I2C_INC_COMPASSREAD(x) inv_iio_counters_compassread(x) +#define INV_I2C_INC_COMPASSWRITE(x) inv_iio_counters_compasswrite(x) + +#define INV_I2C_INC_TEMPREAD(x) inv_iio_counters_tempread(x) + +#define INV_I2C_SETIRQ(type, irq) inv_iio_counters_set_i2cirq(type, irq) +#define INV_I2C_INC_COMPASSIRQ() inv_iio_counters_compassirq() +#define INV_I2C_INC_ACCELIRQ() inv_iio_counters_accelirq() + +void inv_iio_counters_mpuread(int count); +void inv_iio_counters_mpuwrite(int count); +void inv_iio_counters_accelread(int count); +void inv_iio_counters_accelwrite(int count); +void inv_iio_counters_compassread(int count); +void inv_iio_counters_compasswrite(int count); + +void inv_iio_counters_tempread(int count); + +void inv_iio_counters_set_i2cirq(enum irqtype type, int irq); +void inv_iio_counters_compassirq(void); +void inv_iio_counters_accelirq(void); + +#else + +#define INV_I2C_INC_MPUREAD(x) +#define INV_I2C_INC_MPUWRITE(x) +#define INV_I2C_INC_ACCELREAD(x) +#define INV_I2C_INC_ACCELWRITE(x) +#define INV_I2C_INC_COMPASSREAD(x) +#define INV_I2C_INC_COMPASSWRITE(x) + +#define INV_I2C_INC_TEMPREAD(x) + +#define INV_I2C_SETIRQ(type, irq) +#define INV_I2C_INC_COMPASSIRQ() +#define INV_I2C_INC_ACCELIRQ() + +#endif /* CONFIG_INV_TESTING */ + +#endif /* _INV_COUNTERS_H_ */ + diff --git a/drivers/media/platform/msm/ais/isp/msm_isp_axi_util.c b/drivers/media/platform/msm/ais/isp/msm_isp_axi_util.c index bfccb06407f7..f135cfcd6ccd 100644 --- a/drivers/media/platform/msm/ais/isp/msm_isp_axi_util.c +++ b/drivers/media/platform/msm/ais/isp/msm_isp_axi_util.c @@ -2838,7 +2838,6 @@ static int msm_isp_start_axi_stream(struct vfe_device *vfe_dev, msm_isp_get_stream_wm_mask(stream_info, &wm_reload_mask); spin_lock_irqsave(&stream_info->lock, flags); - msm_isp_reset_framedrop(vfe_dev, stream_info); rc = msm_isp_init_stream_ping_pong_reg(vfe_dev, stream_info); if (rc < 0) { pr_err("%s: No buffer for stream%d\n", __func__, @@ -3586,6 +3585,11 @@ int msm_isp_axi_output_cfg(struct vfe_device *vfe_dev, void *arg) pstream_info, plane_idx); } + vfe_dev->hw_info->vfe_ops.axi_ops.cfg_framedrop( + vfe_dev->vfe_base, pstream_info, + pCmd->output_path_cfg[axi_src_idx].framedrop_pattern, + pCmd->output_path_cfg[axi_src_idx].framedrop_period); + if (axi_src_idx <= PIX_ENCODER && axi_src_idx <= IDEAL_RAW) { if (axi_src_idx == CAMIF_RAW) { vfe_dev->axi_data.src_info[VFE_PIX_0]. @@ -3626,6 +3630,29 @@ int msm_isp_axi_output_cfg(struct vfe_device *vfe_dev, void *arg) return rc; } +void msm_isp_framedrop_update(struct vfe_device *vfe_dev, void *arg) +{ + struct msm_vfe_axi_framedrop_update *pCmd = arg; + struct msm_vfe_axi_shared_data *axi_data = &vfe_dev->axi_data; + struct msm_vfe_axi_stream *pstream_info; + + pr_debug("%s: entry\n", __func__); + + if (pCmd->stream_src < VFE_AXI_SRC_MAX) { + + pstream_info = &axi_data->stream_info[pCmd->stream_src]; + + vfe_dev->hw_info->vfe_ops.axi_ops.cfg_framedrop( + vfe_dev->vfe_base, pstream_info, + pCmd->framedrop_pattern, + pCmd->framedrop_period); + + vfe_dev->hw_info->vfe_ops.core_ops.reg_update( + vfe_dev, SRC_TO_INTF(pstream_info->stream_src)); + } + + pr_debug("%s: exit\n", __func__); +} int msm_isp_update_axi_stream(struct vfe_device *vfe_dev, void *arg) { diff --git a/drivers/media/platform/msm/ais/isp/msm_isp_axi_util.h b/drivers/media/platform/msm/ais/isp/msm_isp_axi_util.h index 7babd750a05a..d695c4c0edf3 100644 --- a/drivers/media/platform/msm/ais/isp/msm_isp_axi_util.h +++ b/drivers/media/platform/msm/ais/isp/msm_isp_axi_util.h @@ -84,6 +84,7 @@ int msm_isp_axi_restart(struct vfe_device *vfe_dev, int msm_isp_axi_output_cfg(struct vfe_device *vfe_dev, void *arg); +void msm_isp_framedrop_update(struct vfe_device *vfe_dev, void *arg); void msm_isp_axi_stream_update(struct vfe_device *vfe_dev, enum msm_vfe_input_src frame_src); diff --git a/drivers/media/platform/msm/ais/isp/msm_isp_util.c b/drivers/media/platform/msm/ais/isp/msm_isp_util.c index 64a3c7cde26b..a9b6e5e6a861 100644 --- a/drivers/media/platform/msm/ais/isp/msm_isp_util.c +++ b/drivers/media/platform/msm/ais/isp/msm_isp_util.c @@ -1073,6 +1073,11 @@ static long msm_isp_ioctl_unlocked(struct v4l2_subdev *sd, rc = msm_isp_camif_cfg(vfe_dev, arg); mutex_unlock(&vfe_dev->core_mutex); break; + case VIDIOC_MSM_ISP_FRAMEDROP_UPDATE: + mutex_lock(&vfe_dev->core_mutex); + msm_isp_framedrop_update(vfe_dev, arg); + mutex_unlock(&vfe_dev->core_mutex); + break; case MSM_SD_NOTIFY_FREEZE: vfe_dev->isp_sof_debug = 0; vfe_dev->isp_raw0_debug = 0; diff --git a/drivers/misc/qseecom.c b/drivers/misc/qseecom.c index ce47780e5936..f0140e8bbe68 100644 --- a/drivers/misc/qseecom.c +++ b/drivers/misc/qseecom.c @@ -8729,6 +8729,7 @@ exit_unreg_chrdev_region: static int qseecom_remove(struct platform_device *pdev) { struct qseecom_registered_kclient_list *kclient = NULL; + struct qseecom_registered_kclient_list *kclient_tmp = NULL; unsigned long flags = 0; int ret = 0; int i; @@ -8738,10 +8739,8 @@ static int qseecom_remove(struct platform_device *pdev) atomic_set(&qseecom.qseecom_state, QSEECOM_STATE_NOT_READY); spin_lock_irqsave(&qseecom.registered_kclient_list_lock, flags); - list_for_each_entry(kclient, &qseecom.registered_kclient_list_head, - list) { - if (!kclient) - goto exit_irqrestore; + list_for_each_entry_safe(kclient, kclient_tmp, + &qseecom.registered_kclient_list_head, list) { /* Break the loop if client handle is NULL */ if (!kclient->handle) @@ -8765,7 +8764,7 @@ exit_free_kc_handle: kzfree(kclient->handle); exit_free_kclient: kzfree(kclient); -exit_irqrestore: + spin_unlock_irqrestore(&qseecom.registered_kclient_list_lock, flags); if (qseecom.qseos_version > QSEEE_VERSION_00) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 13e0df67d3b7..0747f22ce56c 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3995,6 +3995,7 @@ cmdq_switch: pr_err("%s: %s: mmc_blk_cmdq_switch failed: %d\n", mmc_hostname(host), __func__, err); ret = err; + goto out; } cmdq_unhalt: err = mmc_cmdq_halt(host, false); diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 9d68f01a2487..81a781c1f9d6 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -802,19 +802,23 @@ static int msm_init_cm_dll(struct sdhci_host *host) | CORE_CK_OUT_EN), host->ioaddr + msm_host_offset->CORE_DLL_CONFIG); - wait_cnt = 50; - /* Wait until DLL_LOCK bit of DLL_STATUS register becomes '1' */ - while (!(readl_relaxed(host->ioaddr + - msm_host_offset->CORE_DLL_STATUS) & CORE_DLL_LOCK)) { - /* max. wait for 50us sec for LOCK bit to be set */ - if (--wait_cnt == 0) { - pr_err("%s: %s: DLL failed to LOCK\n", - mmc_hostname(mmc), __func__); - rc = -ETIMEDOUT; - goto out; + /* For hs400es mode, no need to wait for core dll lock */ + if (!(msm_host->enhanced_strobe && + mmc_card_strobe(msm_host->mmc->card))) { + wait_cnt = 50; + /* Wait until DLL_LOCK bit of DLL_STATUS register becomes '1' */ + while (!(readl_relaxed(host->ioaddr + + msm_host_offset->CORE_DLL_STATUS) & CORE_DLL_LOCK)) { + /* max. wait for 50us sec for LOCK bit to be set */ + if (--wait_cnt == 0) { + pr_err("%s: %s: DLL failed to LOCK\n", + mmc_hostname(mmc), __func__); + rc = -ETIMEDOUT; + goto out; + } + /* wait for 1us before polling again */ + udelay(1); } - /* wait for 1us before polling again */ - udelay(1); } out: @@ -3167,7 +3171,10 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) | CORE_HC_SELECT_IN_EN), host->ioaddr + msm_host_offset->CORE_VENDOR_SPEC); } - if (!host->mmc->ios.old_rate && !msm_host->use_cdclp533) { + /* No need to check for DLL lock for HS400es mode */ + if (!host->mmc->ios.old_rate && !msm_host->use_cdclp533 && + !((card && mmc_card_strobe(card) && + msm_host->enhanced_strobe))) { /* * Poll on DLL_LOCK and DDR_DLL_LOCK bits in * CORE_DLL_STATUS to be set. This should get set diff --git a/drivers/platform/msm/ipa/ipa_v2/ipa_dp.c b/drivers/platform/msm/ipa/ipa_v2/ipa_dp.c index 826d449edbd2..e470183fc3b5 100644 --- a/drivers/platform/msm/ipa/ipa_v2/ipa_dp.c +++ b/drivers/platform/msm/ipa/ipa_v2/ipa_dp.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -2115,8 +2115,10 @@ static void ipa_replenish_rx_cache(struct ipa_sys_context *sys) goto fail_dma_mapping; } + spin_lock_bh(&sys->spinlock); list_add_tail(&rx_pkt->link, &sys->head_desc_list); rx_len_cached = ++sys->len; + spin_unlock_bh(&sys->spinlock); ret = sps_transfer_one(sys->ep->ep_hdl, rx_pkt->data.dma_addr, sys->rx_buff_sz, rx_pkt, 0); @@ -2130,8 +2132,10 @@ static void ipa_replenish_rx_cache(struct ipa_sys_context *sys) return; fail_sps_transfer: + spin_lock_bh(&sys->spinlock); list_del(&rx_pkt->link); rx_len_cached = --sys->len; + spin_unlock_bh(&sys->spinlock); dma_unmap_single(ipa_ctx->pdev, rx_pkt->data.dma_addr, sys->rx_buff_sz, DMA_FROM_DEVICE); fail_dma_mapping: @@ -2171,8 +2175,10 @@ static void ipa_replenish_rx_cache_recycle(struct ipa_sys_context *sys) goto fail_dma_mapping; } + spin_lock_bh(&sys->spinlock); list_add_tail(&rx_pkt->link, &sys->head_desc_list); rx_len_cached = ++sys->len; + spin_unlock_bh(&sys->spinlock); ret = sps_transfer_one(sys->ep->ep_hdl, rx_pkt->data.dma_addr, sys->rx_buff_sz, rx_pkt, 0); @@ -2185,9 +2191,11 @@ static void ipa_replenish_rx_cache_recycle(struct ipa_sys_context *sys) return; fail_sps_transfer: + spin_lock_bh(&sys->spinlock); rx_len_cached = --sys->len; list_del(&rx_pkt->link); INIT_LIST_HEAD(&rx_pkt->link); + spin_unlock_bh(&sys->spinlock); dma_unmap_single(ipa_ctx->pdev, rx_pkt->data.dma_addr, sys->rx_buff_sz, DMA_FROM_DEVICE); fail_dma_mapping: @@ -2219,7 +2227,9 @@ static void ipa_fast_replenish_rx_cache(struct ipa_sys_context *sys) } rx_pkt = sys->repl.cache[curr]; + spin_lock_bh(&sys->spinlock); list_add_tail(&rx_pkt->link, &sys->head_desc_list); + spin_unlock_bh(&sys->spinlock); ret = sps_transfer_one(sys->ep->ep_hdl, rx_pkt->data.dma_addr, sys->rx_buff_sz, rx_pkt, 0); @@ -2278,6 +2288,7 @@ static void ipa_cleanup_rx(struct ipa_sys_context *sys) u32 head; u32 tail; + spin_lock_bh(&sys->spinlock); list_for_each_entry_safe(rx_pkt, r, &sys->head_desc_list, link) { list_del(&rx_pkt->link); @@ -2295,6 +2306,7 @@ static void ipa_cleanup_rx(struct ipa_sys_context *sys) sys->free_skb(rx_pkt->data.skb); kmem_cache_free(ipa_ctx->rx_pkt_wrapper_cache, rx_pkt); } + spin_unlock_bh(&sys->spinlock); if (sys->repl.cache) { head = atomic_read(&sys->repl.head_idx); @@ -2970,8 +2982,10 @@ static void ipa_wq_rx_common(struct ipa_sys_context *sys, u32 size) struct ipa_rx_pkt_wrapper *rx_pkt_expected; struct sk_buff *rx_skb; + spin_lock_bh(&sys->spinlock); if (unlikely(list_empty(&sys->head_desc_list))) { WARN_ON(1); + spin_unlock_bh(&sys->spinlock); return; } rx_pkt_expected = list_first_entry(&sys->head_desc_list, @@ -2979,6 +2993,7 @@ static void ipa_wq_rx_common(struct ipa_sys_context *sys, u32 size) link); list_del(&rx_pkt_expected->link); sys->len--; + spin_unlock_bh(&sys->spinlock); if (size) rx_pkt_expected->len = size; rx_skb = rx_pkt_expected->data.skb; @@ -2999,8 +3014,10 @@ static void ipa_wlan_wq_rx_common(struct ipa_sys_context *sys, u32 size) struct ipa_rx_pkt_wrapper *rx_pkt_expected; struct sk_buff *rx_skb; + spin_lock_bh(&sys->spinlock); if (unlikely(list_empty(&sys->head_desc_list))) { WARN_ON(1); + spin_unlock_bh(&sys->spinlock); return; } rx_pkt_expected = list_first_entry(&sys->head_desc_list, @@ -3008,6 +3025,7 @@ static void ipa_wlan_wq_rx_common(struct ipa_sys_context *sys, u32 size) link); list_del(&rx_pkt_expected->link); sys->len--; + spin_unlock_bh(&sys->spinlock); if (size) rx_pkt_expected->len = size; diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c index 2e9ff2afcba2..1974d6ee032b 100644 --- a/drivers/power/power_supply_sysfs.c +++ b/drivers/power/power_supply_sysfs.c @@ -305,6 +305,7 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(pd_voltage_max), POWER_SUPPLY_ATTR(pd_voltage_min), POWER_SUPPLY_ATTR(sdp_current_max), + POWER_SUPPLY_ATTR(fcc_stepper_enable), /* Local extensions of type int64_t */ POWER_SUPPLY_ATTR(charge_counter_ext), /* Properties of type `const char *' */ diff --git a/drivers/power/supply/qcom/battery.c b/drivers/power/supply/qcom/battery.c index e068bec8b85e..6d5308b3dd0b 100644 --- a/drivers/power/supply/qcom/battery.c +++ b/drivers/power/supply/qcom/battery.c @@ -40,12 +40,14 @@ #define ICL_CHANGE_VOTER "ICL_CHANGE_VOTER" #define PL_INDIRECT_VOTER "PL_INDIRECT_VOTER" #define USBIN_I_VOTER "USBIN_I_VOTER" +#define FCC_STEPPER_VOTER "FCC_STEPPER_VOTER" struct pl_data { int pl_mode; int slave_pct; int taper_pct; int slave_fcc_ua; + int main_fcc_ua; int restricted_current; bool restricted_charging_enabled; struct votable *fcc_votable; @@ -59,6 +61,7 @@ struct pl_data { struct work_struct pl_disable_forever_work; struct delayed_work pl_taper_work; struct delayed_work pl_awake_work; + struct delayed_work fcc_step_update_work; struct power_supply *main_psy; struct power_supply *pl_psy; struct power_supply *batt_psy; @@ -66,6 +69,13 @@ struct pl_data { int charge_type; int total_settled_ua; int pl_settled_ua; + int fcc_step_update; + int main_step_fcc_dir; + int main_step_fcc_count; + int main_step_fcc_residual; + int parallel_step_fcc_dir; + int parallel_step_fcc_count; + int parallel_step_fcc_residual; struct class qcom_batt_class; struct wakeup_source *pl_ws; struct notifier_block nb; @@ -380,6 +390,10 @@ done: * FCC * **********/ #define EFFICIENCY_PCT 80 +#define FCC_STEP_SIZE_UA 100000 +#define FCC_STEP_UPDATE_DELAY_MS 1000 +#define STEP_UP 1 +#define STEP_DOWN -1 static void get_fcc_split(struct pl_data *chip, int total_ua, int *master_ua, int *slave_ua) { @@ -432,6 +446,43 @@ static void get_fcc_split(struct pl_data *chip, int total_ua, *slave_ua = (*slave_ua * chip->taper_pct) / 100; } +static void get_fcc_step_update_params(struct pl_data *chip, int main_fcc_ua, + int parallel_fcc_ua) +{ + union power_supply_propval pval = {0, }; + int rc; + + /* Read current FCC of main charger */ + rc = power_supply_get_property(chip->main_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); + if (rc < 0) { + pr_err("Couldn't get main charger current fcc, rc=%d\n", rc); + return; + } + chip->main_fcc_ua = pval.intval; + + chip->main_step_fcc_dir = (main_fcc_ua > pval.intval) ? + STEP_UP : STEP_DOWN; + chip->main_step_fcc_count = abs((main_fcc_ua - pval.intval) / + FCC_STEP_SIZE_UA); + chip->main_step_fcc_residual = (main_fcc_ua - pval.intval) % + FCC_STEP_SIZE_UA; + + chip->parallel_step_fcc_dir = (parallel_fcc_ua > chip->slave_fcc_ua) ? + STEP_UP : STEP_DOWN; + chip->parallel_step_fcc_count = abs((parallel_fcc_ua - + chip->slave_fcc_ua) / FCC_STEP_SIZE_UA); + chip->parallel_step_fcc_residual = (parallel_fcc_ua - + chip->slave_fcc_ua) % FCC_STEP_SIZE_UA; + + pr_debug("Main FCC Stepper parameters: main_step_direction: %d, main_step_count: %d, main_residual_fcc: %d\n", + chip->main_step_fcc_dir, chip->main_step_fcc_count, + chip->main_step_fcc_residual); + pr_debug("Parallel FCC Stepper parameters: parallel_step_direction: %d, parallel_step_count: %d, parallel_residual_fcc: %d\n", + chip->parallel_step_fcc_dir, chip->parallel_step_fcc_count, + chip->parallel_step_fcc_residual); +} + static int pl_fcc_vote_callback(struct votable *votable, void *data, int total_fcc_ua, const char *client) { @@ -445,80 +496,305 @@ static int pl_fcc_vote_callback(struct votable *votable, void *data, if (!chip->main_psy) return 0; + if (!chip->batt_psy) { + chip->batt_psy = power_supply_get_by_name("battery"); + if (!chip->batt_psy) + return 0; + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE, &pval); + if (rc < 0) { + pr_err("Couldn't read FCC step update status, rc=%d\n", + rc); + return rc; + } + chip->fcc_step_update = pval.intval; + pr_debug("FCC Stepper %s\n", + pval.intval ? "enabled" : "disabled"); + } + + if (chip->fcc_step_update) + cancel_delayed_work_sync(&chip->fcc_step_update_work); + + if (chip->pl_mode == POWER_SUPPLY_PL_NONE || get_effective_result_locked(chip->pl_disable_votable)) { + if (chip->fcc_step_update) { + vote(chip->pl_awake_votable, FCC_STEPPER_VOTER, + true, 0); + get_fcc_step_update_params(chip, total_fcc_ua, 0); + schedule_delayed_work(&chip->fcc_step_update_work, 0); + + return 0; + } pval.intval = total_fcc_ua; rc = power_supply_set_property(chip->main_psy, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); if (rc < 0) pr_err("Couldn't set main fcc, rc=%d\n", rc); + return rc; } if (chip->pl_mode != POWER_SUPPLY_PL_NONE) { get_fcc_split(chip, total_fcc_ua, &master_fcc_ua, &slave_fcc_ua); + if (chip->fcc_step_update) { + vote(chip->pl_awake_votable, FCC_STEPPER_VOTER, + true, 0); + get_fcc_step_update_params(chip, master_fcc_ua, + slave_fcc_ua); + schedule_delayed_work(&chip->fcc_step_update_work, 0); + } else { + /* + * If there is an increase in slave share + * (Also handles parallel enable case) + * Set Main ICL then slave FCC + * else + * (Also handles parallel disable case) + * Set slave ICL then main FCC. + */ + if (slave_fcc_ua > chip->slave_fcc_ua) { + pval.intval = master_fcc_ua; + rc = power_supply_set_property(chip->main_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + &pval); + if (rc < 0) { + pr_err("Could not set main fcc, rc=%d\n", + rc); + return rc; + } - /* - * If there is an increase in slave share - * (Also handles parallel enable case) - * Set Main ICL then slave FCC - * else - * (Also handles parallel disable case) - * Set slave ICL then main FCC. - */ - if (slave_fcc_ua > chip->slave_fcc_ua) { - pval.intval = master_fcc_ua; - rc = power_supply_set_property(chip->main_psy, + pval.intval = slave_fcc_ua; + rc = power_supply_set_property(chip->pl_psy, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); - if (rc < 0) { - pr_err("Could not set main fcc, rc=%d\n", rc); - return rc; + if (rc < 0) { + pr_err("Couldn't set parallel fcc, rc=%d\n", + rc); + return rc; + } + + chip->slave_fcc_ua = slave_fcc_ua; + } else { + pval.intval = slave_fcc_ua; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + &pval); + if (rc < 0) { + pr_err("Couldn't set parallel fcc, rc=%d\n", + rc); + return rc; + } + + chip->slave_fcc_ua = slave_fcc_ua; + + pval.intval = master_fcc_ua; + rc = power_supply_set_property(chip->main_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + &pval); + if (rc < 0) { + pr_err("Could not set main fcc, rc=%d\n", + rc); + return rc; + } } + } + } + + pl_dbg(chip, PR_PARALLEL, "master_fcc=%d slave_fcc=%d distribution=(%d/%d)\n", + master_fcc_ua, slave_fcc_ua, + (master_fcc_ua * 100) / total_fcc_ua, + (slave_fcc_ua * 100) / total_fcc_ua); + + return 0; +} + +static void fcc_step_update_work(struct work_struct *work) +{ + struct pl_data *chip = container_of(work, + struct pl_data, fcc_step_update_work.work); + union power_supply_propval pval = {0, }; + int reschedule_ms = 0, rc = 0; + int main_fcc = chip->main_fcc_ua; + int parallel_fcc = chip->slave_fcc_ua; + + if (!chip->usb_psy) { + chip->usb_psy = power_supply_get_by_name("usb"); + if (!chip->usb_psy) { + pr_err("Couldn't get usb psy\n"); + return; + } + } + + /* Check whether USB is present or not */ + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + if (rc < 0) { + pr_err("Couldn't get USB Present status, rc=%d\n", rc); + return; + } + + /* + * If USB is not present, then disable parallel and + * Main FCC to the effective value of FCC votable and exit. + */ + if (!pval.intval) { + /* Disable parallel */ + parallel_fcc = 0; + pval.intval = 1; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval); + if (rc < 0) + pr_err("Couldn't change slave suspend state rc=%d\n", + rc); + + main_fcc = get_effective_result_locked(chip->fcc_votable); + pval.intval = main_fcc; + rc = power_supply_set_property(chip->main_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); + if (rc < 0) { + pr_err("Couldn't set main charger fcc, rc=%d\n", rc); + return; + } + + goto stepper_exit; + } + + if (chip->main_step_fcc_count) { + main_fcc += (FCC_STEP_SIZE_UA * chip->main_step_fcc_dir); + chip->main_step_fcc_count--; + reschedule_ms = FCC_STEP_UPDATE_DELAY_MS; + } else if (chip->main_step_fcc_residual) { + main_fcc += chip->main_step_fcc_residual; + chip->main_step_fcc_residual = 0; + } - pval.intval = slave_fcc_ua; + if (chip->parallel_step_fcc_count) { + parallel_fcc += (FCC_STEP_SIZE_UA * + chip->parallel_step_fcc_dir); + chip->parallel_step_fcc_count--; + reschedule_ms = FCC_STEP_UPDATE_DELAY_MS; + } else if (chip->parallel_step_fcc_residual) { + parallel_fcc += chip->parallel_step_fcc_residual; + chip->parallel_step_fcc_residual = 0; + } + + if (chip->pl_mode == POWER_SUPPLY_PL_NONE || + get_effective_result_locked(chip->pl_disable_votable)) { + /* Set Parallel FCC */ + pval.intval = parallel_fcc; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); + if (rc < 0) { + pr_err("Couldn't set parallel charger fcc, rc=%d\n", + rc); + return; + } + + /* Set main FCC */ + pval.intval = main_fcc; + rc = power_supply_set_property(chip->main_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); + if (rc < 0) { + pr_err("Couldn't set main charger fcc, rc=%d\n", rc); + return; + } + + if (parallel_fcc < MINIMUM_PARALLEL_FCC_UA) { + pval.intval = 1; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval); + if (rc < 0) { + pr_err("Couldn't change slave suspend state rc=%d\n", + rc); + return; + } + } + } else { + if (parallel_fcc < chip->slave_fcc_ua) { + pval.intval = parallel_fcc; rc = power_supply_set_property(chip->pl_psy, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); if (rc < 0) { - pr_err("Couldn't set parallel fcc, rc=%d\n", - rc); - return rc; + pr_err("Couldn't set parallel charger fcc, rc=%d\n", + rc); + return; } - chip->slave_fcc_ua = slave_fcc_ua; + pval.intval = main_fcc; + rc = power_supply_set_property(chip->main_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + &pval); + if (rc < 0) { + pr_err("Couldn't set main charger fcc, rc=%d\n", + rc); + return; + } } else { - pval.intval = slave_fcc_ua; - rc = power_supply_set_property(chip->pl_psy, + pval.intval = main_fcc; + rc = power_supply_set_property(chip->main_psy, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); if (rc < 0) { - pr_err("Couldn't set parallel fcc, rc=%d\n", - rc); - return rc; + pr_err("Couldn't set main charger fcc, rc=%d\n", + rc); + return; } - chip->slave_fcc_ua = slave_fcc_ua; - - pval.intval = master_fcc_ua; - rc = power_supply_set_property(chip->main_psy, + pval.intval = parallel_fcc; + rc = power_supply_set_property(chip->pl_psy, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); if (rc < 0) { - pr_err("Could not set main fcc, rc=%d\n", rc); - return rc; + pr_err("Couldn't set parallel charger fcc, rc=%d\n", + rc); + return; + } + } + + rc = power_supply_get_property(chip->pl_psy, + POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval); + if (rc < 0) { + pr_err("Couldn't get slave suspend status, rc=%d\n", + rc); + return; + } + + /* + * Enable parallel charger only if it was disabled earlier and + * configured slave fcc is greater than or equal to 100mA. + */ + if (pval.intval == 1 && parallel_fcc >= 100000) { + pval.intval = 0; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval); + if (rc < 0) { + pr_err("Couldn't change slave suspend state rc=%d\n", + rc); + return; } + + if ((chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN) || + (chip->pl_mode == + POWER_SUPPLY_PL_USBIN_USBIN_EXT)) + split_settled(chip); } } - pl_dbg(chip, PR_PARALLEL, "master_fcc=%d slave_fcc=%d distribution=(%d/%d)\n", - master_fcc_ua, slave_fcc_ua, - (master_fcc_ua * 100) / total_fcc_ua, - (slave_fcc_ua * 100) / total_fcc_ua); +stepper_exit: + chip->main_fcc_ua = main_fcc; + chip->slave_fcc_ua = parallel_fcc; - return 0; + if (reschedule_ms) { + schedule_delayed_work(&chip->fcc_step_update_work, + msecs_to_jiffies(reschedule_ms)); + pr_debug("Rescheduling FCC_STEPPER work\n"); + } else { + vote(chip->pl_awake_votable, FCC_STEPPER_VOTER, false, 0); + } } #define PARALLEL_FLOAT_VOLTAGE_DELTA_UV 50000 @@ -660,6 +936,9 @@ static int pl_disable_vote_callback(struct votable *votable, chip->total_settled_ua = 0; chip->pl_settled_ua = 0; + /* Cancel FCC step change work */ + cancel_delayed_work_sync(&chip->fcc_step_update_work); + if (!pl_disable) { /* enable */ /* keep system awake to talk to slave charger through i2c */ cancel_delayed_work_sync(&chip->pl_awake_work); @@ -685,16 +964,19 @@ static int pl_disable_vote_callback(struct votable *votable, * PARALLEL_PSY_VOTER keeps it disabled unless a pl_psy * is seen. */ - pval.intval = 0; - rc = power_supply_set_property(chip->pl_psy, + if (!chip->fcc_step_update) { + pval.intval = 0; + rc = power_supply_set_property(chip->pl_psy, POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval); - if (rc < 0) - pr_err("Couldn't change slave suspend state rc=%d\n", - rc); + if (rc < 0) + pr_err("Couldn't change slave suspend state rc=%d\n", + rc); - if ((chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN) - || (chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT)) - split_settled(chip); + if ((chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN) || + (chip->pl_mode == + POWER_SUPPLY_PL_USBIN_USBIN_EXT)) + split_settled(chip); + } /* * we could have been enabled while in taper mode, * start the taper work if so @@ -715,15 +997,18 @@ static int pl_disable_vote_callback(struct votable *votable, || (chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT)) split_settled(chip); - /* pl_psy may be NULL while in the disable branch */ - if (chip->pl_psy) { - pval.intval = 1; - rc = power_supply_set_property(chip->pl_psy, + if (!chip->fcc_step_update) { + /* pl_psy may be NULL while in the disable branch */ + if (chip->pl_psy) { + pval.intval = 1; + rc = power_supply_set_property(chip->pl_psy, POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval); - if (rc < 0) - pr_err("Couldn't change slave suspend state rc=%d\n", - rc); + if (rc < 0) + pr_err("Couldn't change slave suspend state rc=%d\n", + rc); + } } + rerun_election(chip->fcc_votable); rerun_election(chip->fv_votable); @@ -1118,6 +1403,7 @@ int qcom_batt_init(void) INIT_DELAYED_WORK(&chip->pl_taper_work, pl_taper_work); INIT_WORK(&chip->pl_disable_forever_work, pl_disable_forever_work); INIT_DELAYED_WORK(&chip->pl_awake_work, pl_awake_work); + INIT_DELAYED_WORK(&chip->fcc_step_update_work, fcc_step_update_work); rc = pl_register_notifier(chip); if (rc < 0) { @@ -1172,6 +1458,7 @@ void qcom_batt_deinit(void) cancel_delayed_work_sync(&chip->pl_taper_work); cancel_work_sync(&chip->pl_disable_forever_work); cancel_delayed_work_sync(&chip->pl_awake_work); + cancel_delayed_work_sync(&chip->fcc_step_update_work); power_supply_unreg_notifier(&chip->nb); destroy_votable(chip->pl_enable_votable_indirect); diff --git a/drivers/power/supply/qcom/qpnp-smb2.c b/drivers/power/supply/qcom/qpnp-smb2.c index 02b1204789bf..8e57bf9d2c31 100644 --- a/drivers/power/supply/qcom/qpnp-smb2.c +++ b/drivers/power/supply/qcom/qpnp-smb2.c @@ -325,6 +325,9 @@ static int smb2_parse_dt(struct smb2 *chip) if (rc < 0) chg->otg_delay_ms = OTG_DEFAULT_DEGLITCH_TIME_MS; + chg->fcc_stepper_mode = of_property_read_bool(node, + "qcom,fcc-stepping-enable"); + return 0; } @@ -941,6 +944,7 @@ static enum power_supply_property smb2_batt_props[] = { POWER_SUPPLY_PROP_RERUN_AICL, POWER_SUPPLY_PROP_DP_DM, POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE, }; static int smb2_batt_get_prop(struct power_supply *psy, @@ -1049,6 +1053,9 @@ static int smb2_batt_get_prop(struct power_supply *psy, case POWER_SUPPLY_PROP_CHARGE_COUNTER: rc = smblib_get_prop_batt_charge_counter(chg, val); break; + case POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE: + val->intval = chg->fcc_stepper_mode; + break; default: pr_err("batt power supply prop %d not supported\n", psp); return -EINVAL; diff --git a/drivers/power/supply/qcom/smb-lib.c b/drivers/power/supply/qcom/smb-lib.c index 8eee480e4380..e96523a4d43e 100644 --- a/drivers/power/supply/qcom/smb-lib.c +++ b/drivers/power/supply/qcom/smb-lib.c @@ -3360,7 +3360,8 @@ void smblib_usb_plugin_locked(struct smb_charger *chg) rc = smblib_request_dpdm(chg, true); if (rc < 0) smblib_err(chg, "Couldn't to enable DPDM rc=%d\n", rc); - + if (chg->fcc_stepper_mode) + vote(chg->fcc_votable, FCC_STEPPER_VOTER, false, 0); /* Schedule work to enable parallel charger */ vote(chg->awake_votable, PL_DELAY_VOTER, true, 0); schedule_delayed_work(&chg->pl_enable_work, @@ -3379,6 +3380,11 @@ void smblib_usb_plugin_locked(struct smb_charger *chg) } } + /* Force 1500mA FCC on removal */ + if (chg->fcc_stepper_mode) + vote(chg->fcc_votable, FCC_STEPPER_VOTER, + true, 1500000); + rc = smblib_request_dpdm(chg, false); if (rc < 0) smblib_err(chg, "Couldn't disable DPDM rc=%d\n", rc); diff --git a/drivers/power/supply/qcom/smb-lib.h b/drivers/power/supply/qcom/smb-lib.h index 0de99b9da7bd..4475ccc21a2a 100644 --- a/drivers/power/supply/qcom/smb-lib.h +++ b/drivers/power/supply/qcom/smb-lib.h @@ -67,6 +67,7 @@ enum print_reason { #define WEAK_CHARGER_VOTER "WEAK_CHARGER_VOTER" #define WBC_VOTER "WBC_VOTER" #define OV_VOTER "OV_VOTER" +#define FCC_STEPPER_VOTER "FCC_STEPPER_VOTER" #define VCONN_MAX_ATTEMPTS 3 #define OTG_MAX_ATTEMPTS 3 @@ -346,6 +347,7 @@ struct smb_charger { u8 float_cfg; bool use_extcon; bool otg_present; + bool fcc_stepper_mode; /* workaround flag */ u32 wa_flags; diff --git a/drivers/soc/qcom/hab/Kconfig b/drivers/soc/qcom/hab/Kconfig index 2e4f5114e29f..2e6126f3734e 100644 --- a/drivers/soc/qcom/hab/Kconfig +++ b/drivers/soc/qcom/hab/Kconfig @@ -5,3 +5,7 @@ config MSM_HAB Required for drivers to use the HAB API to communicate with the host OS. +config MSM_AGL + bool "Enable built-in hab config" + help + Use built-in configuration to setup hab driver. diff --git a/drivers/soc/qcom/hab/Makefile b/drivers/soc/qcom/hab/Makefile index 0ad19931776c..945ae52de196 100644 --- a/drivers/soc/qcom/hab/Makefile +++ b/drivers/soc/qcom/hab/Makefile @@ -8,9 +8,24 @@ msm_hab-objs = \ hab_mimex.o \ hab_mem_linux.o \ hab_pipe.o \ - qvm_comm.o \ - hab_qvm.o \ hab_parser.o \ khab_test.o -obj-$(CONFIG_MSM_HAB) += msm_hab.o +ifdef CONFIG_GHS_VMM +msm_hab_hyp-objs = \ + ghs_comm.o \ + hab_ghs.o + +ifndef CONFIG_MSM_AGL +ccflags-y += -DHABMM_HC_VMID +endif + +else +ifdef CONFIG_MSM_GVM_QUIN +msm_hab_hyp-objs = \ + qvm_comm.o \ + hab_qvm.o +endif +endif + +obj-$(CONFIG_MSM_HAB) += msm_hab.o msm_hab_hyp.o diff --git a/drivers/soc/qcom/hab/ghs_comm.c b/drivers/soc/qcom/hab/ghs_comm.c new file mode 100644 index 000000000000..825f33a23858 --- /dev/null +++ b/drivers/soc/qcom/hab/ghs_comm.c @@ -0,0 +1,141 @@ +/* Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "hab.h" +#include "hab_ghs.h" + +int physical_channel_read(struct physical_channel *pchan, + void *payload, + size_t read_size) +{ + struct ghs_vdev *dev = (struct ghs_vdev *)pchan->hyp_data; + + /* size in header is only for payload excluding the header itself */ + if (dev->read_size < read_size + sizeof(struct hab_header)) { + pr_warn("read %zd is less than requested %zd plus header %zd\n", + dev->read_size, read_size, sizeof(struct hab_header)); + read_size = dev->read_size; + } + + /* always skip the header */ + memcpy(payload, (unsigned char *)dev->read_data + + sizeof(struct hab_header) + dev->read_offset, read_size); + dev->read_offset += read_size; + + return read_size; +} + +int physical_channel_send(struct physical_channel *pchan, + struct hab_header *header, + void *payload) +{ + int sizebytes = HAB_HEADER_GET_SIZE(*header); + struct ghs_vdev *dev = (struct ghs_vdev *)pchan->hyp_data; + GIPC_Result result; + uint8_t *msg; + + spin_lock_bh(&dev->io_lock); + + result = GIPC_PrepareMessage(dev->endpoint, sizebytes+sizeof(*header), + (void **)&msg); + if (result == GIPC_Full) { + spin_unlock_bh(&dev->io_lock); + /* need to wait for space! */ + pr_err("failed to reserve send msg for %zd bytes\n", + sizebytes+sizeof(*header)); + return -EBUSY; + } else if (result != GIPC_Success) { + spin_unlock_bh(&dev->io_lock); + pr_err("failed to send due to error %d\n", result); + return -ENOMEM; + } + + if (HAB_HEADER_GET_TYPE(*header) == HAB_PAYLOAD_TYPE_PROFILE) { + struct timeval tv; + struct habmm_xing_vm_stat *pstat = + (struct habmm_xing_vm_stat *)payload; + + do_gettimeofday(&tv); + pstat->tx_sec = tv.tv_sec; + pstat->tx_usec = tv.tv_usec; + } + + memcpy(msg, header, sizeof(*header)); + + if (sizebytes) + memcpy(msg+sizeof(*header), payload, sizebytes); + + result = GIPC_IssueMessage(dev->endpoint, sizebytes+sizeof(*header), + header->id_type_size); + spin_unlock_bh(&dev->io_lock); + if (result != GIPC_Success) { + pr_err("send error %d, sz %zd, prot %x\n", + result, sizebytes+sizeof(*header), + header->id_type_size); + return -EAGAIN; + } + + return 0; +} + +void physical_channel_rx_dispatch(unsigned long physical_channel) +{ + struct hab_header header; + struct physical_channel *pchan = + (struct physical_channel *)physical_channel; + struct ghs_vdev *dev = (struct ghs_vdev *)pchan->hyp_data; + GIPC_Result result; + + uint32_t events; + unsigned long flags; + + spin_lock_irqsave(&pchan->rxbuf_lock, flags); + events = kgipc_dequeue_events(dev->endpoint); + spin_unlock_irqrestore(&pchan->rxbuf_lock, flags); + + if (events & (GIPC_EVENT_RESET)) + pr_err("hab gipc %s remote vmid %d RESET\n", + dev->name, pchan->vmid_remote); + if (events & (GIPC_EVENT_RESETINPROGRESS)) + pr_err("hab gipc %s remote vmid %d RESETINPROGRESS\n", + dev->name, pchan->vmid_remote); + + if (events & (GIPC_EVENT_RECEIVEREADY)) { + spin_lock_bh(&pchan->rxbuf_lock); + while (1) { + dev->read_size = 0; + dev->read_offset = 0; + result = GIPC_ReceiveMessage(dev->endpoint, + dev->read_data, + GIPC_RECV_BUFF_SIZE_BYTES, + &dev->read_size, + &header.id_type_size); + + if (result == GIPC_Success || dev->read_size > 0) { + /* handle corrupted msg? */ + hab_msg_recv(pchan, dev->read_data); + continue; + } else if (result == GIPC_Empty) { + /* no more pending msg */ + break; + } + pr_err("recv unhandled result %d, size %zd\n", + result, dev->read_size); + break; + } + spin_unlock_bh(&pchan->rxbuf_lock); + } + + if (events & (GIPC_EVENT_SENDREADY)) + pr_debug("kgipc send ready\n"); +} diff --git a/drivers/soc/qcom/hab/hab.c b/drivers/soc/qcom/hab/hab.c index 52de57b766f2..48d61870f776 100644 --- a/drivers/soc/qcom/hab/hab.c +++ b/drivers/soc/qcom/hab/hab.c @@ -16,11 +16,13 @@ .name = __name__,\ .id = __id__,\ .pchannels = LIST_HEAD_INIT(hab_devices[__num__].pchannels),\ - .pchan_lock = __MUTEX_INITIALIZER(hab_devices[__num__].pchan_lock),\ + .pchan_lock = __SPIN_LOCK_UNLOCKED(hab_devices[__num__].pchan_lock),\ .openq_list = LIST_HEAD_INIT(hab_devices[__num__].openq_list),\ .openlock = __SPIN_LOCK_UNLOCKED(&hab_devices[__num__].openlock)\ } +static const char hab_info_str[] = "Change: 16239527 Revision: #65"; + /* * The following has to match habmm definitions, order does not matter if * hab config does not care either. When hab config is not present, the default @@ -54,6 +56,8 @@ static struct hab_device hab_devices[] = { struct hab_driver hab_driver = { .ndevices = ARRAY_SIZE(hab_devices), .devp = hab_devices, + .uctx_list = LIST_HEAD_INIT(hab_driver.uctx_list), + .drvlock = __SPIN_LOCK_UNLOCKED(hab_driver.drvlock), }; struct uhab_context *hab_ctx_alloc(int kernel) @@ -77,6 +81,7 @@ struct uhab_context *hab_ctx_alloc(int kernel) rwlock_init(&ctx->exp_lock); rwlock_init(&ctx->ctx_lock); + INIT_LIST_HEAD(&ctx->pending_open); kref_init(&ctx->refcount); ctx->import_ctx = habmem_imp_hyp_open(); if (!ctx->import_ctx) { @@ -86,14 +91,53 @@ struct uhab_context *hab_ctx_alloc(int kernel) } ctx->kernel = kernel; + spin_lock_bh(&hab_driver.drvlock); + list_add_tail(&ctx->node, &hab_driver.uctx_list); + hab_driver.ctx_cnt++; + ctx->lb_be = hab_driver.b_loopback_be; /* loopback only */ + hab_driver.b_loopback_be = ~hab_driver.b_loopback_be; /* loopback only*/ + spin_unlock_bh(&hab_driver.drvlock); + pr_debug("ctx %pK live %d loopback be %d\n", + ctx, hab_driver.ctx_cnt, ctx->lb_be); + return ctx; } +/* ctx can only be freed when all the vchan releases the refcnt */ void hab_ctx_free(struct kref *ref) { struct uhab_context *ctx = container_of(ref, struct uhab_context, refcount); struct hab_export_ack_recvd *ack_recvd, *tmp; + struct virtual_channel *vchan; + struct physical_channel *pchan; + int i; + struct uhab_context *ctxdel, *ctxtmp; + struct hab_open_node *node; + struct export_desc *exp, *exp_tmp; + + /* garbage-collect exp/imp buffers */ + write_lock(&ctx->exp_lock); + list_for_each_entry_safe(exp, exp_tmp, &ctx->exp_whse, node) { + list_del(&exp->node); + pr_debug("potential leak exp %d vcid %X recovered\n", + exp->export_id, exp->vcid_local); + habmem_hyp_revoke(exp->payload, exp->payload_count); + habmem_remove_export(exp); + } + write_unlock(&ctx->exp_lock); + + spin_lock_bh(&ctx->imp_lock); + list_for_each_entry_safe(exp, exp_tmp, &ctx->imp_whse, node) { + list_del(&exp->node); + ctx->import_total--; + pr_debug("leaked imp %d vcid %X for ctx is collected total %d\n", + exp->export_id, exp->vcid_local, + ctx->import_total); + habmm_imp_hyp_unmap(ctx->import_ctx, exp, ctx->kernel); + kfree(exp); + } + spin_unlock_bh(&ctx->imp_lock); habmem_imp_hyp_close(ctx->import_ctx, ctx->kernel); @@ -102,9 +146,70 @@ void hab_ctx_free(struct kref *ref) kfree(ack_recvd); } + /* walk vchan list to find the leakage */ + spin_lock_bh(&hab_driver.drvlock); + hab_driver.ctx_cnt--; + list_for_each_entry_safe(ctxdel, ctxtmp, &hab_driver.uctx_list, node) { + if (ctxdel == ctx) + list_del(&ctxdel->node); + } + spin_unlock_bh(&hab_driver.drvlock); + pr_debug("live ctx %d refcnt %d kernel %d close %d owner %d\n", + hab_driver.ctx_cnt, get_refcnt(ctx->refcount), + ctx->kernel, ctx->closing, ctx->owner); + + /* check vchans in this ctx */ + write_lock(&ctx->ctx_lock); + list_for_each_entry(vchan, &ctx->vchannels, node) { + pr_warn("leak vchan id %X cnt %X remote %d in ctx\n", + vchan->id, get_refcnt(vchan->refcount), + vchan->otherend_id); + } + write_unlock(&ctx->ctx_lock); + + /* check pending open */ + if (ctx->pending_cnt) + pr_warn("potential leak of pendin_open nodes %d\n", + ctx->pending_cnt); + + write_lock(&ctx->ctx_lock); + list_for_each_entry(node, &ctx->pending_open, node) { + pr_warn("leak pending open vcid %X type %d subid %d openid %d\n", + node->request.xdata.vchan_id, node->request.type, + node->request.xdata.sub_id, + node->request.xdata.open_id); + } + write_unlock(&ctx->ctx_lock); + + /* check vchans belong to this ctx in all hab/mmid devices */ + for (i = 0; i < hab_driver.ndevices; i++) { + struct hab_device *habdev = &hab_driver.devp[i]; + + spin_lock_bh(&habdev->pchan_lock); + list_for_each_entry(pchan, &habdev->pchannels, node) { + + /* check vchan ctx owner */ + write_lock(&pchan->vchans_lock); + list_for_each_entry(vchan, &pchan->vchannels, pnode) { + if (vchan->ctx == ctx) { + pr_warn("leak vcid %X cnt %d pchan %s local %d remote %d\n", + vchan->id, + get_refcnt(vchan->refcount), + pchan->name, pchan->vmid_local, + pchan->vmid_remote); + } + } + write_unlock(&pchan->vchans_lock); + } + spin_unlock_bh(&habdev->pchan_lock); + } kfree(ctx); } +/* + * caller needs to call vchan_put() afterwards. this is used to refcnt + * the local ioctl access based on ctx + */ struct virtual_channel *hab_get_vchan_fromvcid(int32_t vcid, struct uhab_context *ctx) { @@ -140,14 +245,14 @@ struct hab_device *find_hab_device(unsigned int mm_id) * frontend backend * send(INIT) wait(INIT) * wait(INIT_ACK) send(INIT_ACK) - * send(ACK) wait(ACK) + * send(INIT_DONE) wait(INIT_DONE) */ struct virtual_channel *frontend_open(struct uhab_context *ctx, unsigned int mm_id, int dom_id) { - int ret, open_id = 0; + int ret, ret2, open_id = 0; struct physical_channel *pchan = NULL; struct hab_device *dev; struct virtual_channel *vchan = NULL; @@ -155,6 +260,7 @@ struct virtual_channel *frontend_open(struct uhab_context *ctx, struct hab_open_request request; struct hab_open_request *recv_request; int sub_id = HAB_MMID_GET_MINOR(mm_id); + struct hab_open_node pending_open = { { 0 } }; dev = find_hab_device(mm_id); if (dev == NULL) { @@ -163,6 +269,7 @@ struct virtual_channel *frontend_open(struct uhab_context *ctx, goto err; } + /* guest can find its own id */ pchan = hab_pchan_find_domid(dev, dom_id); if (!pchan) { pr_err("hab_pchan_find_domid failed: dom_id=%d\n", dom_id); @@ -170,44 +277,83 @@ struct virtual_channel *frontend_open(struct uhab_context *ctx, goto err; } - vchan = hab_vchan_alloc(ctx, pchan); + open_id = atomic_inc_return(&open_id_counter); + vchan = hab_vchan_alloc(ctx, pchan, open_id); if (!vchan) { pr_err("vchan alloc failed\n"); ret = -ENOMEM; goto err; - } + } else /* Send Init sequence */ - open_id = atomic_inc_return(&open_id_counter); hab_open_request_init(&request, HAB_PAYLOAD_TYPE_INIT, pchan, vchan->id, sub_id, open_id); + request.xdata.ver_fe = HAB_API_VER; ret = hab_open_request_send(&request); if (ret) { pr_err("hab_open_request_send failed: %d\n", ret); goto err; } + pending_open.request = request; + + /* during wait app could be terminated */ + hab_open_pending_enter(ctx, pchan, &pending_open); + /* Wait for Init-Ack sequence */ hab_open_request_init(&request, HAB_PAYLOAD_TYPE_INIT_ACK, pchan, 0, sub_id, open_id); + /* wait forever */ ret = hab_open_listen(ctx, dev, &request, &recv_request, 0); - if (ret || !recv_request) { - pr_err("hab_open_listen failed: %d\n", ret); + if (!ret && recv_request && ((recv_request->xdata.ver_fe & 0xFFFF0000) + != (recv_request->xdata.ver_be & 0xFFFF0000))) { + /* version check */ + pr_err("hab major version mismatch fe %X be %X on mmid %d\n", + recv_request->xdata.ver_fe, + recv_request->xdata.ver_be, mm_id); + + hab_open_pending_exit(ctx, pchan, &pending_open); + ret = -EPROTO; + goto err; + } else if (ret || !recv_request) { + pr_err("hab_open_listen failed: %d, send cancel vcid %x subid %d openid %d\n", + ret, vchan->id, + sub_id, open_id); + /* send cancel to BE due to FE's local close */ + hab_open_request_init(&request, HAB_PAYLOAD_TYPE_INIT_CANCEL, + pchan, vchan->id, sub_id, open_id); + request.xdata.ver_fe = HAB_API_VER; + ret2 = hab_open_request_send(&request); + if (ret2) + pr_err("send init_cancel failed %d on vcid %x\n", ret2, + vchan->id); + hab_open_pending_exit(ctx, pchan, &pending_open); + + if (ret != -EINTR) + ret = -EINVAL; goto err; } - vchan->otherend_id = recv_request->vchan_id; - hab_open_request_free(recv_request); + /* remove pending open locally after good pairing */ + hab_open_pending_exit(ctx, pchan, &pending_open); + + pr_debug("hab version match fe %X be %X on mmid %d\n", + recv_request->xdata.ver_fe, recv_request->xdata.ver_be, + mm_id); - vchan->session_id = open_id; - pr_debug("vchan->session_id:%d\n", vchan->session_id); + vchan->otherend_id = recv_request->xdata.vchan_id; + hab_open_request_free(recv_request); /* Send Ack sequence */ - hab_open_request_init(&request, HAB_PAYLOAD_TYPE_ACK, pchan, + hab_open_request_init(&request, HAB_PAYLOAD_TYPE_INIT_DONE, pchan, 0, sub_id, open_id); + request.xdata.ver_fe = HAB_API_VER; ret = hab_open_request_send(&request); - if (ret) + if (ret) { + pr_err("failed to send init-done vcid %x remote %x openid %d\n", + vchan->id, vchan->otherend_id, vchan->session_id); goto err; + } hab_pchan_put(pchan); @@ -222,10 +368,10 @@ err: } struct virtual_channel *backend_listen(struct uhab_context *ctx, - unsigned int mm_id) + unsigned int mm_id, int timeout) { - int ret; - int open_id; + int ret, ret2; + int open_id, ver_fe; int sub_id = HAB_MMID_GET_MINOR(mm_id); struct physical_channel *pchan = NULL; struct hab_device *dev; @@ -233,6 +379,7 @@ struct virtual_channel *backend_listen(struct uhab_context *ctx, struct hab_open_request request; struct hab_open_request *recv_request; uint32_t otherend_vchan_id; + struct hab_open_node pending_open = { { 0 } }; dev = find_hab_device(mm_id); if (dev == NULL) { @@ -245,19 +392,50 @@ struct virtual_channel *backend_listen(struct uhab_context *ctx, /* Wait for Init sequence */ hab_open_request_init(&request, HAB_PAYLOAD_TYPE_INIT, NULL, 0, sub_id, 0); - ret = hab_open_listen(ctx, dev, &request, &recv_request, 0); + /* cancel should not happen at this moment */ + ret = hab_open_listen(ctx, dev, &request, &recv_request, + timeout); if (ret || !recv_request) { - pr_err("hab_open_listen failed: %d\n", ret); + if (!ret && !recv_request) + ret = -EINVAL; + if (-EAGAIN == ret) { + ret = -ETIMEDOUT; + } else { + /* device is closed */ + pr_err("open request wait failed ctx closing %d\n", + ctx->closing); + } + goto err; + } else if (!ret && recv_request && + ((recv_request->xdata.ver_fe & 0xFFFF0000) != + (HAB_API_VER & 0xFFFF0000))) { + int ret2; + /* version check */ + pr_err("version mismatch fe %X be %X on mmid %d\n", + recv_request->xdata.ver_fe, HAB_API_VER, mm_id); + hab_open_request_init(&request, + HAB_PAYLOAD_TYPE_INIT_ACK, + NULL, 0, sub_id, recv_request->xdata.open_id); + request.xdata.ver_be = HAB_API_VER; + /* reply to allow FE to bail out */ + ret2 = hab_open_request_send(&request); + if (ret2) + pr_err("send FE version mismatch failed mmid %d sub %d\n", + mm_id, sub_id); + ret = -EPROTO; goto err; } - otherend_vchan_id = recv_request->vchan_id; - open_id = recv_request->open_id; + /* guest id from guest */ + otherend_vchan_id = recv_request->xdata.vchan_id; + open_id = recv_request->xdata.open_id; + ver_fe = recv_request->xdata.ver_fe; pchan = recv_request->pchan; hab_pchan_get(pchan); hab_open_request_free(recv_request); + recv_request = NULL; - vchan = hab_vchan_alloc(ctx, pchan); + vchan = hab_vchan_alloc(ctx, pchan, open_id); if (!vchan) { ret = -ENOMEM; goto err; @@ -265,23 +443,64 @@ struct virtual_channel *backend_listen(struct uhab_context *ctx, vchan->otherend_id = otherend_vchan_id; - vchan->session_id = open_id; - pr_debug("vchan->session_id:%d\n", vchan->session_id); - /* Send Init-Ack sequence */ hab_open_request_init(&request, HAB_PAYLOAD_TYPE_INIT_ACK, pchan, vchan->id, sub_id, open_id); + request.xdata.ver_fe = ver_fe; /* carry over */ + request.xdata.ver_be = HAB_API_VER; ret = hab_open_request_send(&request); if (ret) goto err; + pending_open.request = request; + /* wait only after init-ack is sent */ + hab_open_pending_enter(ctx, pchan, &pending_open); + /* Wait for Ack sequence */ - hab_open_request_init(&request, HAB_PAYLOAD_TYPE_ACK, + hab_open_request_init(&request, HAB_PAYLOAD_TYPE_INIT_DONE, pchan, 0, sub_id, open_id); - ret = hab_open_listen(ctx, dev, &request, &recv_request, 0); - - if (ret != -EAGAIN) + ret = hab_open_listen(ctx, dev, &request, &recv_request, + HAB_HS_TIMEOUT); + hab_open_pending_exit(ctx, pchan, &pending_open); + if (ret && recv_request && + recv_request->type == HAB_PAYLOAD_TYPE_INIT_CANCEL) { + pr_err("listen cancelled vcid %x subid %d openid %d ret %d\n", + request.xdata.vchan_id, request.xdata.sub_id, + request.xdata.open_id, ret); + + /* FE cancels this session. + * So BE has to cancel its too + */ + hab_open_request_init(&request, + HAB_PAYLOAD_TYPE_INIT_CANCEL, pchan, + vchan->id, sub_id, open_id); + ret2 = hab_open_request_send(&request); + if (ret2) + pr_err("send init_ack failed %d on vcid %x\n", + ret2, vchan->id); + hab_open_pending_exit(ctx, pchan, &pending_open); + + ret = -ENODEV; /* open request cancelled remotely */ break; + } else if (ret != -EAGAIN) { + hab_open_pending_exit(ctx, pchan, &pending_open); + break; /* received something. good case! */ + } + + /* stay in the loop retry */ + pr_warn("retry open ret %d vcid %X remote %X sub %d open %d\n", + ret, vchan->id, vchan->otherend_id, sub_id, open_id); + + /* retry path starting here. free previous vchan */ + hab_open_request_init(&request, HAB_PAYLOAD_TYPE_INIT_CANCEL, + pchan, vchan->id, sub_id, open_id); + request.xdata.ver_fe = ver_fe; + request.xdata.ver_be = HAB_API_VER; + ret2 = hab_open_request_send(&request); + if (ret2) + pr_err("send init_ack failed %d on vcid %x\n", ret2, + vchan->id); + hab_open_pending_exit(ctx, pchan, &pending_open); hab_vchan_put(vchan); vchan = NULL; @@ -290,7 +509,7 @@ struct virtual_channel *backend_listen(struct uhab_context *ctx, } if (ret || !recv_request) { - pr_err("backend_listen failed: %d\n", ret); + pr_err("backend mmid %d listen error %d\n", mm_id, ret); ret = -EINVAL; goto err; } @@ -299,7 +518,8 @@ struct virtual_channel *backend_listen(struct uhab_context *ctx, hab_pchan_put(pchan); return vchan; err: - pr_err("listen on mmid %d failed\n", mm_id); + if (ret != -ETIMEDOUT) + pr_err("listen on mmid %d failed\n", mm_id); if (vchan) hab_vchan_put(vchan); if (pchan) @@ -318,8 +538,9 @@ long hab_vchan_send(struct uhab_context *ctx, struct hab_header header = HAB_HEADER_INITIALIZER; int nonblocking_flag = flags & HABMM_SOCKET_SEND_FLAGS_NON_BLOCKING; - if (sizebytes > HAB_MAX_MSG_SIZEBYTES) { - pr_err("Message too large, %lu bytes\n", sizebytes); + if (sizebytes > HAB_HEADER_SIZE_MASK) { + pr_err("Message too large, %lu bytes, max is %d\n", + sizebytes, HAB_HEADER_SIZE_MASK); return -EINVAL; } @@ -330,11 +551,17 @@ long hab_vchan_send(struct uhab_context *ctx, } HAB_HEADER_SET_SIZE(header, sizebytes); - if (flags & HABMM_SOCKET_SEND_FLAGS_XING_VM_STAT) + if (flags & HABMM_SOCKET_SEND_FLAGS_XING_VM_STAT) { HAB_HEADER_SET_TYPE(header, HAB_PAYLOAD_TYPE_PROFILE); - else + if (sizebytes < sizeof(struct habmm_xing_vm_stat)) { + pr_err("wrong profiling buffer size %zd, expect %zd\n", + sizebytes, + sizeof(struct habmm_xing_vm_stat)); + return -EINVAL; + } + } else { HAB_HEADER_SET_TYPE(header, HAB_PAYLOAD_TYPE_MSG); - + } HAB_HEADER_SET_ID(header, vchan->otherend_id); HAB_HEADER_SET_SESSION_ID(header, vchan->session_id); @@ -347,8 +574,6 @@ long hab_vchan_send(struct uhab_context *ctx, schedule(); } - - err: if (vchan) hab_vchan_put(vchan); @@ -403,23 +628,22 @@ bool hab_is_loopback(void) int hab_vchan_open(struct uhab_context *ctx, unsigned int mmid, int32_t *vcid, + int32_t timeout, uint32_t flags) { struct virtual_channel *vchan = NULL; struct hab_device *dev; - pr_debug("Open mmid=%d, loopback mode=%d, loopback num=%d\n", - mmid, hab_driver.b_loopback, hab_driver.loopback_num); + pr_debug("Open mmid=%d, loopback mode=%d, loopback be ctx %d\n", + mmid, hab_driver.b_loopback, ctx->lb_be); if (!vcid) return -EINVAL; if (hab_is_loopback()) { - if (!hab_driver.loopback_num) { - hab_driver.loopback_num = 1; - vchan = backend_listen(ctx, mmid); + if (ctx->lb_be) { + vchan = backend_listen(ctx, mmid, timeout); } else { - hab_driver.loopback_num = 0; vchan = frontend_open(ctx, mmid, LOOPBACK_DOM); } } else { @@ -427,28 +651,37 @@ int hab_vchan_open(struct uhab_context *ctx, if (dev) { struct physical_channel *pchan = - hab_pchan_find_domid(dev, HABCFG_VMID_DONT_CARE); - - if (pchan->is_be) - vchan = backend_listen(ctx, mmid); - else - vchan = frontend_open(ctx, mmid, - HABCFG_VMID_DONT_CARE); + hab_pchan_find_domid(dev, + HABCFG_VMID_DONT_CARE); + if (pchan) { + if (pchan->is_be) + vchan = backend_listen(ctx, mmid, + timeout); + else + vchan = frontend_open(ctx, mmid, + HABCFG_VMID_DONT_CARE); + } else { + pr_err("open on nonexistent pchan (mmid %x)", + mmid); + return -ENODEV; + } } else { pr_err("failed to find device, mmid %d\n", mmid); } } if (IS_ERR(vchan)) { - pr_err("vchan open failed over mmid=%d\n", mmid); + if (-ETIMEDOUT != PTR_ERR(vchan) && -EAGAIN != PTR_ERR(vchan)) + pr_err("vchan open failed mmid=%d\n", mmid); return PTR_ERR(vchan); } - pr_debug("vchan id %x, remote id %x\n", - vchan->id, vchan->otherend_id); + pr_debug("vchan id %x remote id %x session %d\n", vchan->id, + vchan->otherend_id, vchan->session_id); write_lock(&ctx->ctx_lock); list_add_tail(&vchan->node, &ctx->vchannels); + ctx->vcnt++; write_unlock(&ctx->ctx_lock); *vcid = vchan->id; @@ -469,17 +702,6 @@ void hab_send_close_msg(struct virtual_channel *vchan) } } -static void hab_vchan_close_impl(struct kref *ref) -{ - struct virtual_channel *vchan = - container_of(ref, struct virtual_channel, usagecnt); - - list_del(&vchan->node); - hab_vchan_stop_notify(vchan); - hab_vchan_put(vchan); -} - - void hab_vchan_close(struct uhab_context *ctx, int32_t vcid) { struct virtual_channel *vchan, *tmp; @@ -490,11 +712,29 @@ void hab_vchan_close(struct uhab_context *ctx, int32_t vcid) write_lock(&ctx->ctx_lock); list_for_each_entry_safe(vchan, tmp, &ctx->vchannels, node) { if (vchan->id == vcid) { - kref_put(&vchan->usagecnt, hab_vchan_close_impl); + write_unlock(&ctx->ctx_lock); + pr_debug("vcid %x remote %x session %d refcnt %d\n", + vchan->id, vchan->otherend_id, + vchan->session_id, get_refcnt(vchan->refcount)); + /* + * only set when vc close is called locally by user + * explicity. Used to block remote msg. if forked once + * before, this local close is skipped due to child + * usage. if forked but not closed locally, the local + * context could NOT be closed, vchan can be prolonged + * by arrived remote msgs + */ + if (vchan->forked) + vchan->forked = 0; + else { + vchan->closed = 1; + hab_vchan_stop_notify(vchan); + } + hab_vchan_put(vchan); /* there is a lock inside */ + write_lock(&ctx->ctx_lock); break; } } - write_unlock(&ctx->ctx_lock); } @@ -511,7 +751,7 @@ static int hab_initialize_pchan_entry(struct hab_device *mmid_device, char pchan_name[MAX_VMID_NAME_SIZE]; struct physical_channel *pchan = NULL; int ret; - int vmid = is_be ? vmid_remote : vmid_local; + int vmid = is_be ? vmid_remote : vmid_local; /* used for naming only */ if (!mmid_device) { pr_err("habdev %pK, vmid local %d, remote %d, is be %d\n", @@ -541,7 +781,11 @@ static int hab_initialize_pchan_entry(struct hab_device *mmid_device, return ret; } -static void hab_generate_pchan(struct local_vmid *settings, int i, int j) +/* + * generate pchan list based on hab settings table. + * return status 0: success, otherwise failure + */ +static int hab_generate_pchan(struct local_vmid *settings, int i, int j) { int k, ret = 0; @@ -657,6 +901,7 @@ static void hab_generate_pchan(struct local_vmid *settings, int i, int j) break; } + return ret; } /* @@ -665,7 +910,7 @@ static void hab_generate_pchan(struct local_vmid *settings, int i, int j) */ static int hab_generate_pchan_list(struct local_vmid *settings) { - int i, j; + int i, j, ret = 0; /* scan by valid VMs, then mmid */ pr_debug("self vmid is %d\n", settings->self); @@ -677,24 +922,34 @@ static int hab_generate_pchan_list(struct local_vmid *settings) for (j = 1; j <= HABCFG_MMID_AREA_MAX; j++) { if (HABCFG_GET_MMID(settings, i, j) != HABCFG_VMID_INVALID) - hab_generate_pchan(settings, i, j); + ret = hab_generate_pchan(settings, + i, j); } } } - - return 0; + return ret; } /* * This function checks hypervisor plug-in readiness, read in hab configs, * and configure pchans */ +#ifdef HABMM_HC_VMID +#define DEFAULT_GVMID 3 +#else +#define DEFAULT_GVMID 2 +#endif + int do_hab_parse(void) { int result; int i; struct hab_device *device; - int pchan_total = 0; + + /* single GVM is 2, multigvm is 2 or 3. GHS LV-GVM 2, LA-GVM 3 */ + int default_gvmid = DEFAULT_GVMID; + + pr_debug("hab parse starts for %s\n", hab_info_str); /* first check if hypervisor plug-in is ready */ result = hab_hypervisor_register(); @@ -703,7 +958,10 @@ int do_hab_parse(void) return result; } - /* Initialize open Q before first pchan starts */ + /* + * Initialize open Q before first pchan starts. + * Each is for one pchan list + */ for (i = 0; i < hab_driver.ndevices; i++) { device = &hab_driver.devp[i]; init_waitqueue_head(&device->openq); @@ -712,12 +970,12 @@ int do_hab_parse(void) /* read in hab config and create pchans*/ memset(&hab_driver.settings, HABCFG_VMID_INVALID, sizeof(hab_driver.settings)); - result = hab_parse(&hab_driver.settings); if (result) { - pr_warn("hab_parse failed and use the default settings\n"); - fill_default_gvm_settings(&hab_driver.settings, 2, - MM_AUD_START, MM_ID_MAX); + pr_err("hab config open failed, prepare default gvm %d settings\n", + default_gvmid); + fill_default_gvm_settings(&hab_driver.settings, default_gvmid, + MM_AUD_START, MM_ID_MAX); } /* now generate hab pchan list */ @@ -725,6 +983,7 @@ int do_hab_parse(void) if (result) { pr_err("generate pchan list failed, ret %d\n", result); } else { + int pchan_total = 0; for (i = 0; i < hab_driver.ndevices; i++) { device = &hab_driver.devp[i]; pchan_total += device->pchan_cnt; @@ -736,6 +995,48 @@ int do_hab_parse(void) return result; } +int get_refcnt(struct kref ref) +{ + return ref.refcount.counter; +} + +void hab_hypervisor_unregister_common(void) +{ + int status, i; + struct uhab_context *ctx; + struct virtual_channel *vchan; + + for (i = 0; i < hab_driver.ndevices; i++) { + struct hab_device *habdev = &hab_driver.devp[i]; + struct physical_channel *pchan, *pchan_tmp; + + list_for_each_entry_safe(pchan, pchan_tmp, + &habdev->pchannels, node) { + status = habhyp_commdev_dealloc(pchan); + if (status) { + pr_err("failed to free pchan %pK, i %d, ret %d\n", + pchan, i, status); + } + } + } + + /* detect leaking uctx */ + spin_lock_bh(&hab_driver.drvlock); + list_for_each_entry(ctx, &hab_driver.uctx_list, node) { + pr_warn("leaking ctx owner %d refcnt %d kernel %d\n", + ctx->owner, get_refcnt(ctx->refcount), ctx->kernel); + /* further check vchan leak */ + read_lock(&ctx->ctx_lock); + list_for_each_entry(vchan, &ctx->vchannels, node) { + pr_warn("leaking vchan id %X remote %X refcnt %d\n", + vchan->id, vchan->otherend_id, + get_refcnt(vchan->refcount)); + } + read_unlock(&ctx->ctx_lock); + } + spin_unlock_bh(&hab_driver.drvlock); +} + static int hab_open(struct inode *inodep, struct file *filep) { int result = 0; @@ -749,7 +1050,10 @@ static int hab_open(struct inode *inodep, struct file *filep) return -ENOMEM; } + ctx->owner = task_pid_nr(current); filep->private_data = ctx; + pr_debug("ctx owner %d refcnt %d\n", ctx->owner, + get_refcnt(ctx->refcount)); return result; } @@ -758,25 +1062,50 @@ static int hab_release(struct inode *inodep, struct file *filep) { struct uhab_context *ctx = filep->private_data; struct virtual_channel *vchan, *tmp; + struct hab_open_node *node; if (!ctx) return 0; - pr_debug("inode %pK, filep %pK\n", inodep, filep); + pr_debug("inode %pK, filep %pK ctx %pK\n", inodep, filep, ctx); write_lock(&ctx->ctx_lock); - + /* notify remote side on vchan closing */ list_for_each_entry_safe(vchan, tmp, &ctx->vchannels, node) { - list_del(&vchan->node); + list_del(&vchan->node); /* vchan is not in this ctx anymore */ hab_vchan_stop_notify(vchan); - hab_vchan_put(vchan); + write_unlock(&ctx->ctx_lock); + if (!vchan->closed) { + pr_warn("potential leak vc %pK %x remote %x session %d refcnt %d\n", + vchan, vchan->id, vchan->otherend_id, + vchan->session_id, + get_refcnt(vchan->refcount)); + hab_vchan_put(vchan); /* there is a lock inside */ + } + write_lock(&ctx->ctx_lock); } + /* notify remote side on pending open */ + list_for_each_entry(node, &ctx->pending_open, node) { + /* no touch to the list itself. it is allocated on the stack */ + if (hab_open_cancel_notify(&node->request)) + pr_err("failed to send open cancel vcid %x subid %d openid %d pchan %s\n", + node->request.xdata.vchan_id, + node->request.xdata.sub_id, + node->request.xdata.open_id, + node->request.pchan->habdev->name); + } write_unlock(&ctx->ctx_lock); hab_ctx_put(ctx); filep->private_data = NULL; + /* ctx leak check */ + if (get_refcnt(ctx->refcount)) + pr_warn("pending ctx release owner %d refcnt %d total %d\n", + ctx->owner, get_refcnt(ctx->refcount), + hab_driver.ctx_cnt); + return 0; } @@ -809,7 +1138,9 @@ static long hab_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) case IOCTL_HAB_VC_OPEN: open_param = (struct hab_open *)data; ret = hab_vchan_open(ctx, open_param->mmid, - &open_param->vcid, open_param->flags); + &open_param->vcid, + open_param->timeout, + open_param->flags); break; case IOCTL_HAB_VC_CLOSE: close_param = (struct hab_close *)data; @@ -858,6 +1189,9 @@ static long hab_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) recv_param->sizebytes = 0; ret = -EFAULT; } + } else if (ret && msg) { + pr_warn("vcid %X recv failed %d and msg is still of %zd bytes\n", + recv_param->vcid, (int)ret, msg->sizebytes); } if (msg) @@ -879,22 +1213,22 @@ static long hab_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) info_param = (struct hab_info *)data; if (!info_param->names || !info_param->namesize || info_param->namesize > sizeof(names)) { - pr_err("wrong vm info vcid %X, names %llX, sz %d\n", - info_param->vcid, info_param->names, - info_param->namesize); + pr_err("wrong param for vm info vcid %X, names %llX, sz %d\n", + info_param->vcid, info_param->names, + info_param->namesize); ret = -EINVAL; break; } ret = hab_vchan_query(ctx, info_param->vcid, (uint64_t *)&info_param->ids, - names, info_param->namesize, 0); + names, info_param->namesize, 0); if (!ret) { if (copy_to_user((void __user *)info_param->names, names, info_param->namesize)) { pr_err("copy_to_user failed: vc=%x size=%d\n", - info_param->vcid, - info_param->namesize*2); + info_param->vcid, + info_param->namesize*2); info_param->namesize = 0; ret = -EFAULT; } @@ -904,7 +1238,7 @@ static long hab_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) ret = -ENOIOCTLCMD; } - if (ret == 0 && _IOC_SIZE(cmd) && (cmd & IOC_OUT)) + if (_IOC_SIZE(cmd) && (cmd & IOC_OUT)) if (copy_to_user((void __user *) arg, data, _IOC_SIZE(cmd))) { pr_err("copy_to_user failed: cmd=%x\n", cmd); ret = -EFAULT; @@ -955,6 +1289,26 @@ static const struct dma_map_ops hab_dma_ops = { .unmap_sg = hab_unmap_sg, }; +static int hab_power_down_callback( + struct notifier_block *nfb, unsigned long action, void *data) +{ + + switch (action) { + case SYS_DOWN: + case SYS_HALT: + case SYS_POWER_OFF: + pr_debug("reboot called %ld\n", action); + hab_hypervisor_unregister(); /* only for single VM guest */ + break; + } + pr_debug("reboot called %ld done\n", action); + return NOTIFY_DONE; +} + +static struct notifier_block hab_reboot_notifier = { + .notifier_call = hab_power_down_callback, +}; + static int __init hab_init(void) { int result; @@ -997,6 +1351,10 @@ static int __init hab_init(void) goto err; } + result = register_reboot_notifier(&hab_reboot_notifier); + if (result) + pr_err("failed to register reboot notifier %d\n", result); + /* read in hab config, then configure pchans */ result = do_hab_parse(); @@ -1007,12 +1365,10 @@ static int __init hab_init(void) result = -ENOMEM; hab_hypervisor_unregister(); goto err; - } - - set_dma_ops(hab_driver.dev, &hab_dma_ops); - - return result; + } else + set_dma_ops(hab_driver.dev, &hab_dma_ops); } + return result; err: if (!IS_ERR_OR_NULL(hab_driver.dev)) @@ -1037,6 +1393,8 @@ static void __exit hab_exit(void) class_destroy(hab_driver.class); cdev_del(&hab_driver.cdev); unregister_chrdev_region(dev, 1); + unregister_reboot_notifier(&hab_reboot_notifier); + pr_debug("hab exit called\n"); } subsys_initcall(hab_init); diff --git a/drivers/soc/qcom/hab/hab.h b/drivers/soc/qcom/hab/hab.h index facb0a068221..d1aa88e3978e 100644 --- a/drivers/soc/qcom/hab/hab.h +++ b/drivers/soc/qcom/hab/hab.h @@ -16,7 +16,7 @@ #ifdef pr_fmt #undef pr_fmt #endif -#define pr_fmt(fmt) "|hab:%s:%d|" fmt, __func__, __LINE__ +#define pr_fmt(fmt) "hab:%s:%d " fmt, __func__, __LINE__ #include <linux/types.h> @@ -41,16 +41,19 @@ #include <linux/uaccess.h> #include <linux/dma-direction.h> #include <linux/dma-mapping.h> +#include <linux/jiffies.h> +#include <linux/reboot.h> enum hab_payload_type { HAB_PAYLOAD_TYPE_MSG = 0x0, HAB_PAYLOAD_TYPE_INIT, HAB_PAYLOAD_TYPE_INIT_ACK, - HAB_PAYLOAD_TYPE_ACK, + HAB_PAYLOAD_TYPE_INIT_DONE, HAB_PAYLOAD_TYPE_EXPORT, HAB_PAYLOAD_TYPE_EXPORT_ACK, HAB_PAYLOAD_TYPE_PROFILE, HAB_PAYLOAD_TYPE_CLOSE, + HAB_PAYLOAD_TYPE_INIT_CANCEL, HAB_PAYLOAD_TYPE_MAX, }; #define LOOPBACK_DOM 0xFF @@ -128,21 +131,21 @@ struct hab_header { /* "Size" of the HAB_HEADER_ID and HAB_VCID_ID must match */ #define HAB_HEADER_SIZE_SHIFT 0 #define HAB_HEADER_TYPE_SHIFT 16 -#define HAB_HEADER_ID_SHIFT 24 +#define HAB_HEADER_ID_SHIFT 20 #define HAB_HEADER_SIZE_MASK 0x0000FFFF -#define HAB_HEADER_TYPE_MASK 0x00FF0000 -#define HAB_HEADER_ID_MASK 0xFF000000 +#define HAB_HEADER_TYPE_MASK 0x000F0000 +#define HAB_HEADER_ID_MASK 0xFFF00000 #define HAB_HEADER_INITIALIZER {0} #define HAB_MMID_GET_MAJOR(mmid) (mmid & 0xFFFF) #define HAB_MMID_GET_MINOR(mmid) ((mmid>>16) & 0xFF) #define HAB_VCID_ID_SHIFT 0 -#define HAB_VCID_DOMID_SHIFT 8 -#define HAB_VCID_MMID_SHIFT 16 -#define HAB_VCID_ID_MASK 0x000000FF -#define HAB_VCID_DOMID_MASK 0x0000FF00 -#define HAB_VCID_MMID_MASK 0xFFFF0000 +#define HAB_VCID_DOMID_SHIFT 12 +#define HAB_VCID_MMID_SHIFT 20 +#define HAB_VCID_ID_MASK 0x00000FFF +#define HAB_VCID_DOMID_MASK 0x000FF000 +#define HAB_VCID_MMID_MASK 0xFFF00000 #define HAB_VCID_GET_ID(vcid) \ (((vcid) & HAB_VCID_ID_MASK) >> HAB_VCID_ID_SHIFT) @@ -182,12 +185,14 @@ struct hab_header { #define HAB_HEADER_GET_SESSION_ID(header) ((header).session_id) +#define HAB_HS_TIMEOUT (10*1000*1000) + struct physical_channel { + struct list_head node; char name[MAX_VMID_NAME_SIZE]; int is_be; struct kref refcount; struct hab_device *habdev; - struct list_head node; struct idr vchan_idr; spinlock_t vid_lock; @@ -195,38 +200,44 @@ struct physical_channel { spinlock_t expid_lock; void *hyp_data; - int dom_id; - int vmid_local; + int dom_id; /* BE role: remote vmid; FE role: don't care */ + int vmid_local; /* from DT or hab_config */ int vmid_remote; - char vmname_local[12]; + char vmname_local[12]; /* from DT */ char vmname_remote[12]; int closed; spinlock_t rxbuf_lock; - /* vchans over this pchan */ + /* debug only */ + uint32_t sequence_tx; + uint32_t sequence_rx; + + /* vchans on this pchan */ struct list_head vchannels; + int vcnt; rwlock_t vchans_lock; }; - +/* this payload has to be used together with type */ struct hab_open_send_data { int vchan_id; int sub_id; int open_id; + int ver_fe; + int ver_be; + int reserved; }; struct hab_open_request { int type; struct physical_channel *pchan; - int vchan_id; - int sub_id; - int open_id; + struct hab_open_send_data xdata; }; struct hab_open_node { struct hab_open_request request; struct list_head node; - int age; + int64_t age; /* sec */ }; struct hab_export_ack { @@ -247,20 +258,25 @@ struct hab_message { uint32_t data[]; }; +/* for all the pchans of same kind */ struct hab_device { char name[MAX_VMID_NAME_SIZE]; - unsigned int id; + uint32_t id; struct list_head pchannels; int pchan_cnt; - struct mutex pchan_lock; - struct list_head openq_list; + spinlock_t pchan_lock; + struct list_head openq_list; /* received */ spinlock_t openlock; wait_queue_head_t openq; + int openq_cnt; }; struct uhab_context { + struct list_head node; /* managed by the driver */ struct kref refcount; + struct list_head vchannels; + int vcnt; struct list_head exp_whse; uint32_t export_total; @@ -276,9 +292,15 @@ struct uhab_context { void *import_ctx; + struct list_head pending_open; /* sent to remote */ + int pending_cnt; + rwlock_t ctx_lock; int closing; int kernel; + int owner; + + int lb_be; /* loopback only */ }; /* @@ -297,7 +319,7 @@ struct local_vmid { }; struct hab_driver { - struct device *dev; + struct device *dev; /* mmid dev list */ struct cdev cdev; dev_t major; struct class *class; @@ -305,33 +327,30 @@ struct hab_driver { struct hab_device *devp; struct uhab_context *kctx; + struct list_head uctx_list; + int ctx_cnt; + spinlock_t drvlock; + struct local_vmid settings; /* parser results */ int b_server_dom; - int loopback_num; + int b_loopback_be; /* only allow 2 apps simultaneously 1 fe 1 be */ int b_loopback; void *hyp_priv; /* hypervisor plug-in storage */ }; struct virtual_channel { - struct work_struct work; /* * refcount is used to track the references from hab core to the virtual * channel such as references from physical channels, * i.e. references from the "other" side */ struct kref refcount; - /* - * usagecnt is used to track the clients who are using this virtual - * channel such as local clients, client sowftware etc, - * i.e. references from "this" side - */ - struct kref usagecnt; struct physical_channel *pchan; struct uhab_context *ctx; - struct list_head node; - struct list_head pnode; + struct list_head node; /* for ctx */ + struct list_head pnode; /* for pchan */ struct list_head rx_list; wait_queue_head_t rx_queue; spinlock_t rx_lock; @@ -339,6 +358,14 @@ struct virtual_channel { int otherend_id; int otherend_closed; uint32_t session_id; + + /* + * set when local close() is called explicitly. vchan could be + * used in hab-recv-msg() path (2) then close() is called (1). + * this is same case as close is not called and no msg path + */ + int closed; + int forked; /* if fork is detected and assume only once */ }; /* @@ -351,12 +378,15 @@ struct export_desc { int readonly; uint64_t import_index; - struct virtual_channel *vchan; + struct virtual_channel *vchan; /* vchan could be freed earlier */ + struct uhab_context *ctx; + struct physical_channel *pchan; int32_t vcid_local; int32_t vcid_remote; int domid_local; int domid_remote; + int flags; struct list_head node; void *kva; @@ -365,7 +395,8 @@ struct export_desc { } __packed; int hab_vchan_open(struct uhab_context *ctx, - unsigned int mmid, int32_t *vcid, uint32_t flags); + unsigned int mmid, int32_t *vcid, + int32_t timeout, uint32_t flags); void hab_vchan_close(struct uhab_context *ctx, int32_t vcid); long hab_vchan_send(struct uhab_context *ctx, @@ -401,13 +432,17 @@ int habmem_hyp_grant_user(unsigned long address, int page_count, int flags, int remotedom, - void *ppdata); + void *ppdata, + int *compressed, + int *compressed_size); int habmem_hyp_grant(unsigned long address, int page_count, int flags, int remotedom, - void *ppdata); + void *ppdata, + int *compressed, + int *compressed_size); int habmem_hyp_revoke(void *expdata, uint32_t count); @@ -417,7 +452,7 @@ void habmem_imp_hyp_close(void *priv, int kernel); int habmem_imp_hyp_map(void *imp_ctx, struct hab_import *param, struct export_desc *exp, int kernel); -int habmm_imp_hyp_unmap(void *imp_ctx, struct export_desc *exp); +int habmm_imp_hyp_unmap(void *imp_ctx, struct export_desc *exp, int kernel); int habmem_imp_hyp_mmap(struct file *flip, struct vm_area_struct *vma); @@ -427,7 +462,7 @@ void hab_msg_free(struct hab_message *message); int hab_msg_dequeue(struct virtual_channel *vchan, struct hab_message **msg, int *rsize, unsigned int flags); -void hab_msg_recv(struct physical_channel *pchan, +int hab_msg_recv(struct physical_channel *pchan, struct hab_header *header); void hab_open_request_init(struct hab_open_request *request, @@ -447,7 +482,7 @@ int hab_open_listen(struct uhab_context *ctx, int ms_timeout); struct virtual_channel *hab_vchan_alloc(struct uhab_context *ctx, - struct physical_channel *pchan); + struct physical_channel *pchan, int openid); struct virtual_channel *hab_vchan_get(struct physical_channel *pchan, struct hab_header *header); void hab_vchan_put(struct virtual_channel *vchan); @@ -482,6 +517,7 @@ static inline void hab_ctx_put(struct uhab_context *ctx) void hab_send_close_msg(struct virtual_channel *vchan); int hab_hypervisor_register(void); void hab_hypervisor_unregister(void); +void hab_hypervisor_unregister_common(void); int habhyp_commdev_alloc(void **commdev, int is_be, char *name, int vmid_remote, struct hab_device *mmid_device); int habhyp_commdev_dealloc(void *commdev); @@ -496,7 +532,7 @@ int physical_channel_send(struct physical_channel *pchan, void physical_channel_rx_dispatch(unsigned long physical_channel); -int loopback_pchan_create(char *dev_name); +int loopback_pchan_create(struct hab_device *dev, char *pchan_name); int hab_parse(struct local_vmid *settings); @@ -512,6 +548,21 @@ int hab_vchan_query(struct uhab_context *ctx, int32_t vcid, uint64_t *ids, struct hab_device *find_hab_device(unsigned int mm_id); +int get_refcnt(struct kref ref); + +int hab_open_pending_enter(struct uhab_context *ctx, + struct physical_channel *pchan, + struct hab_open_node *pending); + +int hab_open_pending_exit(struct uhab_context *ctx, + struct physical_channel *pchan, + struct hab_open_node *pending); + +int hab_open_cancel_notify(struct hab_open_request *request); + +int hab_open_receive_cancel(struct physical_channel *pchan, + size_t sizebytes); + /* Global singleton HAB instance */ extern struct hab_driver hab_driver; diff --git a/drivers/soc/qcom/hab/hab_ghs.c b/drivers/soc/qcom/hab/hab_ghs.c new file mode 100644 index 000000000000..11fd6bb92a90 --- /dev/null +++ b/drivers/soc/qcom/hab/hab_ghs.c @@ -0,0 +1,214 @@ +/* Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "hab.h" +#include "hab_ghs.h" + +static const char * const dt_gipc_path_name[] = { + "testgipc1", + "testgipc2", + "testgipc3", + "testgipc4", + "testgipc5", + "testgipc6", + "testgipc7", + "testgipc8", + "testgipc9", + "testgipc10", + "testgipc11", + "testgipc12", + "testgipc13", + "testgipc14", + "testgipc15", + "testgipc16", + "testgipc17", + "testgipc18", + "testgipc19", + "testgipc20", + "testgipc21", + "testgipc22", +}; + +static struct ghs_vmm_plugin_info_s { + const char **dt_name; + int curr; + int probe_cnt; +} ghs_vmm_plugin_info = { + dt_gipc_path_name, + 0, + ARRAY_SIZE(dt_gipc_path_name), +}; + +static void ghs_irq_handler(void *cookie) +{ + struct physical_channel *pchan = cookie; + struct ghs_vdev *dev = + (struct ghs_vdev *) (pchan ? pchan->hyp_data : NULL); + + if (dev) + tasklet_schedule(&dev->task); +} + +/* static struct physical_channel *habhyp_commdev_alloc(int id) */ +int habhyp_commdev_alloc(void **commdev, int is_be, char *name, int vmid_remote, + struct hab_device *mmid_device) +{ + struct ghs_vdev *dev = NULL; + struct physical_channel *pchan = NULL; + struct physical_channel **ppchan = (struct physical_channel **)commdev; + int ret = 0; + + if (ghs_vmm_plugin_info.curr > ghs_vmm_plugin_info.probe_cnt) { + pr_err("too many commdev alloc %d, supported is %d\n", + ghs_vmm_plugin_info.curr, + ghs_vmm_plugin_info.probe_cnt); + ret = -ENOENT; + goto err; + } + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + pr_err("allocate struct ghs_vdev failed %zu bytes on pchan %s\n", + sizeof(*dev), name); + goto err; + } + + memset(dev, 0, sizeof(*dev)); + spin_lock_init(&dev->io_lock); + + /* + * TODO: ExtractEndpoint is in ghs_comm.c because it blocks. + * Extrace and Request should be in roughly the same spot + */ + if (is_be) { + /* role is backend */ + dev->be = 1; + } else { + /* role is FE */ + struct device_node *gvh_dn; + + gvh_dn = of_find_node_by_path("/aliases"); + if (gvh_dn) { + const char *ep_path = NULL; + struct device_node *ep_dn; + + ret = of_property_read_string(gvh_dn, + ghs_vmm_plugin_info.dt_name[ghs_vmm_plugin_info.curr], + &ep_path); + if (ret) + pr_err("failed to read endpoint string ret %d\n", + ret); + of_node_put(gvh_dn); + + ep_dn = of_find_node_by_path(ep_path); + if (ep_dn) { + dev->endpoint = kgipc_endpoint_alloc(ep_dn); + of_node_put(ep_dn); + if (IS_ERR(dev->endpoint)) { + ret = PTR_ERR(dev->endpoint); + pr_err("KGIPC alloc failed id: %d, ret: %d\n", + ghs_vmm_plugin_info.curr, ret); + goto err; + } else { + pr_debug("gipc ep found for %d\n", + ghs_vmm_plugin_info.curr); + } + } else { + pr_err("of_parse_phandle failed id: %d\n", + ghs_vmm_plugin_info.curr); + ret = -ENOENT; + goto err; + } + } else { + pr_err("of_find_compatible_node failed id: %d\n", + ghs_vmm_plugin_info.curr); + ret = -ENOENT; + goto err; + } + } + /* add pchan into the mmid_device list */ + pchan = hab_pchan_alloc(mmid_device, vmid_remote); + if (!pchan) { + pr_err("hab_pchan_alloc failed for %s, cnt %d\n", + mmid_device->name, mmid_device->pchan_cnt); + ret = -ENOMEM; + goto err; + } + pchan->closed = 0; + pchan->hyp_data = (void *)dev; + pchan->is_be = is_be; + strlcpy(dev->name, name, sizeof(dev->name)); + *ppchan = pchan; + dev->read_data = kmalloc(GIPC_RECV_BUFF_SIZE_BYTES, GFP_KERNEL); + if (!dev->read_data) { + ret = -ENOMEM; + goto err; + } + + tasklet_init(&dev->task, physical_channel_rx_dispatch, + (unsigned long) pchan); + + ret = kgipc_endpoint_start_with_irq_callback(dev->endpoint, + ghs_irq_handler, + pchan); + if (ret) { + pr_err("irq alloc failed id: %d %s, ret: %d\n", + ghs_vmm_plugin_info.curr, name, ret); + goto err; + } else + pr_debug("ep irq handler started for %d %s, ret %d\n", + ghs_vmm_plugin_info.curr, name, ret); + /* this value could be more than devp total */ + ghs_vmm_plugin_info.curr++; + return 0; +err: + hab_pchan_put(pchan); + kfree(dev); + return ret; +} + +int habhyp_commdev_dealloc(void *commdev) +{ + struct physical_channel *pchan = (struct physical_channel *)commdev; + struct ghs_vdev *dev = pchan->hyp_data; + + kgipc_endpoint_free(dev->endpoint); + kfree(dev->read_data); + kfree(dev); + + if (get_refcnt(pchan->refcount) > 1) { + pr_warn("potential leak pchan %s vchans %d refcnt %d\n", + pchan->name, pchan->vcnt, get_refcnt(pchan->refcount)); + } + hab_pchan_put(pchan); + return 0; +} + +void hab_hypervisor_unregister(void) +{ + pr_debug("total %d\n", hab_driver.ndevices); + + hab_hypervisor_unregister_common(); + + ghs_vmm_plugin_info.curr = 0; +} + +int hab_hypervisor_register(void) +{ + int ret = 0; + + hab_driver.b_server_dom = 0; + + return ret; +} diff --git a/drivers/soc/qcom/hab/hab_ghs.h b/drivers/soc/qcom/hab/hab_ghs.h new file mode 100644 index 000000000000..54812480ebaa --- /dev/null +++ b/drivers/soc/qcom/hab/hab_ghs.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __HAB_GHS_H +#define __HAB_GHS_H + +#include <ghs_vmm/kgipc.h> +#define GIPC_RECV_BUFF_SIZE_BYTES (32*1024) + +struct ghs_vdev { + int be; + void *read_data; /* buffer to receive from gipc */ + size_t read_size; + int read_offset; + GIPC_Endpoint endpoint; + spinlock_t io_lock; + char name[32]; + struct tasklet_struct task; +}; +#endif /* __HAB_GHS_H */ diff --git a/drivers/soc/qcom/hab/hab_mem_linux.c b/drivers/soc/qcom/hab/hab_mem_linux.c index a779067ee4c4..74ee88a037af 100644 --- a/drivers/soc/qcom/hab/hab_mem_linux.c +++ b/drivers/soc/qcom/hab/hab_mem_linux.c @@ -82,8 +82,6 @@ static int habmem_get_dma_pages_from_va(unsigned long address, goto err; } - pr_debug("vma flags %lx\n", vma->vm_flags); - /* Look for the fd that matches this the vma file */ fd = iterate_fd(current->files, 0, match_file, vma->vm_file); if (fd == 0) { @@ -111,7 +109,6 @@ static int habmem_get_dma_pages_from_va(unsigned long address, for_each_sg(sg_table->sgl, s, sg_table->nents, i) { page = sg_page(s); - pr_debug("sgl length %d\n", s->length); for (j = page_offset; j < (s->length >> PAGE_SHIFT); j++) { pages[rc] = nth_page(page, j); @@ -205,7 +202,9 @@ int habmem_hyp_grant_user(unsigned long address, int page_count, int flags, int remotedom, - void *ppdata) + void *ppdata, + int *compressed, + int *compressed_size) { int i, ret = 0; struct grantable *item = (struct grantable *)ppdata; @@ -239,7 +238,8 @@ int habmem_hyp_grant_user(unsigned long address, for (i = 0; i < page_count; i++) item[i].pfn = page_to_pfn(pages[i]); } else { - pr_err("get %d user pages failed: %d\n", page_count, ret); + pr_err("get %d user pages failed %d flags %d\n", + page_count, ret, flags); } vfree(pages); @@ -256,7 +256,9 @@ int habmem_hyp_grant(unsigned long address, int page_count, int flags, int remotedom, - void *ppdata) + void *ppdata, + int *compressed, + int *compressed_size) { int i; struct grantable *item; @@ -310,7 +312,7 @@ void habmem_imp_hyp_close(void *imp_ctx, int kernel) list_del(&pglist->list); priv->cnt--; - vfree(pglist->pages); + kfree(pglist->pages); kfree(pglist); } @@ -460,19 +462,19 @@ static int habmem_imp_hyp_map_fd(void *imp_ctx, unsigned long pfn; int i, j, k = 0; pgprot_t prot = PAGE_KERNEL; - int32_t fd; + int32_t fd, size; DEFINE_DMA_BUF_EXPORT_INFO(exp_info); if (!pfn_table || !priv) return -EINVAL; - - pages = vmalloc(exp->payload_count * sizeof(struct page *)); + size = exp->payload_count * sizeof(struct page *); + pages = kmalloc(size, GFP_KERNEL); if (!pages) return -ENOMEM; pglist = kzalloc(sizeof(*pglist), GFP_KERNEL); if (!pglist) { - vfree(pages); + kfree(pages); return -ENOMEM; } @@ -503,7 +505,7 @@ static int habmem_imp_hyp_map_fd(void *imp_ctx, exp_info.priv = pglist; pglist->dmabuf = dma_buf_export(&exp_info); if (IS_ERR(pglist->dmabuf)) { - vfree(pages); + kfree(pages); kfree(pglist); return PTR_ERR(pglist->dmabuf); } @@ -511,7 +513,7 @@ static int habmem_imp_hyp_map_fd(void *imp_ctx, fd = dma_buf_fd(pglist->dmabuf, O_CLOEXEC); if (fd < 0) { dma_buf_put(pglist->dmabuf); - vfree(pages); + kfree(pages); kfree(pglist); return -EINVAL; } @@ -539,17 +541,18 @@ static int habmem_imp_hyp_map_kva(void *imp_ctx, struct pages_list *pglist; struct importer_context *priv = imp_ctx; unsigned long pfn; - int i, j, k = 0; + int i, j, k = 0, size; pgprot_t prot = PAGE_KERNEL; if (!pfn_table || !priv) return -EINVAL; - pages = vmalloc(exp->payload_count * sizeof(struct page *)); + size = exp->payload_count * sizeof(struct page *); + pages = kmalloc(size, GFP_KERNEL); if (!pages) return -ENOMEM; pglist = kzalloc(sizeof(*pglist), GFP_KERNEL); if (!pglist) { - vfree(pages); + kfree(pages); return -ENOMEM; } @@ -575,7 +578,7 @@ static int habmem_imp_hyp_map_kva(void *imp_ctx, pglist->kva = vmap(pglist->pages, pglist->npages, VM_MAP, prot); if (pglist->kva == NULL) { - vfree(pages); + kfree(pages); kfree(pglist); pr_err("%ld pages vmap failed\n", pglist->npages); return -ENOMEM; @@ -607,18 +610,18 @@ static int habmem_imp_hyp_map_uva(void *imp_ctx, struct pages_list *pglist; struct importer_context *priv = imp_ctx; unsigned long pfn; - int i, j, k = 0; + int i, j, k = 0, size; if (!pfn_table || !priv) return -EINVAL; - - pages = vmalloc(exp->payload_count * sizeof(struct page *)); + size = exp->payload_count * sizeof(struct page *); + pages = kmalloc(size, GFP_KERNEL); if (!pages) return -ENOMEM; pglist = kzalloc(sizeof(*pglist), GFP_KERNEL); if (!pglist) { - vfree(pages); + kfree(pages); return -ENOMEM; } @@ -670,7 +673,7 @@ int habmem_imp_hyp_map(void *imp_ctx, struct hab_import *param, return ret; } -int habmm_imp_hyp_unmap(void *imp_ctx, struct export_desc *exp) +int habmm_imp_hyp_unmap(void *imp_ctx, struct export_desc *exp, int kernel) { struct importer_context *priv = imp_ctx; struct pages_list *pglist, *tmp; @@ -679,11 +682,8 @@ int habmm_imp_hyp_unmap(void *imp_ctx, struct export_desc *exp) write_lock(&priv->implist_lock); list_for_each_entry_safe(pglist, tmp, &priv->imp_list, list) { if (pglist->export_id == exp->export_id && - pglist->vcid == exp->vcid_remote) { + pglist->vcid == exp->vcid_remote) { found = 1; - } - - if (found) { list_del(&pglist->list); priv->cnt--; break; @@ -705,7 +705,7 @@ int habmm_imp_hyp_unmap(void *imp_ctx, struct export_desc *exp) if (pglist->dmabuf) dma_buf_put(pglist->dmabuf); - vfree(pglist->pages); + kfree(pglist->pages); kfree(pglist); return 0; @@ -719,9 +719,6 @@ int habmem_imp_hyp_mmap(struct file *filp, struct vm_area_struct *vma) struct pages_list *pglist; int bfound = 0; - pr_debug("mmap request start %lX, len %ld, index %lX\n", - vma->vm_start, length, vma->vm_pgoff); - read_lock(&imp_ctx->implist_lock); list_for_each_entry(pglist, &imp_ctx->imp_list, list) { if (pglist->index == vma->vm_pgoff) { diff --git a/drivers/soc/qcom/hab/hab_mimex.c b/drivers/soc/qcom/hab/hab_mimex.c index 00fbeabed4bb..86d763f65657 100644 --- a/drivers/soc/qcom/hab/hab_mimex.c +++ b/drivers/soc/qcom/hab/hab_mimex.c @@ -28,7 +28,7 @@ static int hab_export_ack_find(struct uhab_context *ctx, - struct hab_export_ack *expect_ack) + struct hab_export_ack *expect_ack, struct virtual_channel *vchan) { int ret = 0; struct hab_export_ack_recvd *ack_recvd, *tmp; @@ -36,9 +36,10 @@ static int hab_export_ack_find(struct uhab_context *ctx, spin_lock_bh(&ctx->expq_lock); list_for_each_entry_safe(ack_recvd, tmp, &ctx->exp_rxq, node) { - if (ack_recvd->ack.export_id == expect_ack->export_id && + if ((ack_recvd->ack.export_id == expect_ack->export_id && ack_recvd->ack.vcid_local == expect_ack->vcid_local && - ack_recvd->ack.vcid_remote == expect_ack->vcid_remote) { + ack_recvd->ack.vcid_remote == expect_ack->vcid_remote) + || vchan->otherend_closed) { list_del(&ack_recvd->node); kfree(ack_recvd); ret = 1; @@ -57,15 +58,17 @@ static int hab_export_ack_find(struct uhab_context *ctx, } static int hab_export_ack_wait(struct uhab_context *ctx, - struct hab_export_ack *expect_ack) + struct hab_export_ack *expect_ack, struct virtual_channel *vchan) { int ret; ret = wait_event_interruptible_timeout(ctx->exp_wq, - hab_export_ack_find(ctx, expect_ack), - HZ); + hab_export_ack_find(ctx, expect_ack, vchan), + HAB_HS_TIMEOUT); if (!ret || (ret == -ERESTARTSYS)) ret = -EAGAIN; + else if (vchan->otherend_closed) + ret = -ENODEV; else if (ret > 0) ret = 0; return ret; @@ -86,7 +89,7 @@ static struct export_desc *habmem_add_export(struct virtual_channel *vchan, if (!vchan || !sizebytes) return NULL; - exp = vmalloc(sizebytes); + exp = kzalloc(sizebytes, GFP_KERNEL); if (!exp) return NULL; @@ -103,6 +106,8 @@ static struct export_desc *habmem_add_export(struct virtual_channel *vchan, exp->vcid_remote = vchan->otherend_id; exp->domid_local = -1; /* dom id, provided on the importer */ exp->domid_remote = vchan->pchan->dom_id; + exp->ctx = vchan->ctx; + exp->pchan = vchan->pchan; ctx = vchan->ctx; write_lock(&ctx->exp_lock); @@ -118,19 +123,22 @@ void habmem_remove_export(struct export_desc *exp) struct physical_channel *pchan; struct uhab_context *ctx; - if (!exp || !exp->vchan || !exp->vchan->ctx || !exp->vchan->pchan) + if (!exp || !exp->ctx || !exp->pchan) { + pr_err("failed to find valid info in exp %pK ctx %pK pchan %pK\n", + exp, exp->ctx, exp->pchan); return; + } - ctx = exp->vchan->ctx; + ctx = exp->ctx; ctx->export_total--; - pchan = exp->vchan->pchan; + pchan = exp->pchan; spin_lock(&pchan->expid_lock); idr_remove(&pchan->expid_idr, exp->export_id); spin_unlock(&pchan->expid_lock); - vfree(exp); + kfree(exp); } static int compress_pfns(void **pfns, int npages, unsigned int *data_size) @@ -148,7 +156,7 @@ static int compress_pfns(void **pfns, int npages, unsigned int *data_size) new_table->first_pfn = item[0].pfn; for (i = 1; i < npages; i++) { if (item[i].pfn-1 == item[i-1].pfn) { - region_size++; + region_size++; /* continuous pfn */ } else { new_table->region[j].size = region_size; new_table->region[j].space = item[i].pfn - @@ -208,7 +216,12 @@ static int habmem_export_vchan(struct uhab_context *ctx, expected_ack.export_id = exp->export_id; expected_ack.vcid_local = exp->vcid_local; expected_ack.vcid_remote = exp->vcid_remote; - ret = hab_export_ack_wait(ctx, &expected_ack); + ret = hab_export_ack_wait(ctx, &expected_ack, vchan); + if (ret != 0) { + pr_err("failed to receive remote export ack %d on vc %x\n", + ret, vchan->id); + return ret; + } *export_id = exp->export_id; @@ -225,12 +238,11 @@ int hab_mem_export(struct uhab_context *ctx, uint32_t export_id = 0; struct virtual_channel *vchan; int page_count; + int compressed = 0; - if (!ctx || !param || param->sizebytes > HAB_MAX_EXPORT_SIZE) + if (!ctx || !param) return -EINVAL; - pr_debug("vc %X, mem size %d\n", param->vcid, param->sizebytes); - vchan = hab_get_vchan_fromvcid(param->vcid, ctx); if (!vchan || !vchan->pchan) { ret = -ENODEV; @@ -249,13 +261,17 @@ int hab_mem_export(struct uhab_context *ctx, page_count, param->flags, vchan->pchan->dom_id, - pdata_exp); + pdata_exp, + &compressed, + &pdata_size); } else { ret = habmem_hyp_grant_user((unsigned long)param->buffer, page_count, param->flags, vchan->pchan->dom_id, - pdata_exp); + pdata_exp, + &compressed, + &pdata_size); } if (ret < 0) { pr_err("habmem_hyp_grant failed size=%d ret=%d\n", @@ -263,7 +279,8 @@ int hab_mem_export(struct uhab_context *ctx, goto err; } - compress_pfns(&pdata_exp, page_count, &pdata_size); + if (!compressed) + compress_pfns(&pdata_exp, page_count, &pdata_size); ret = habmem_export_vchan(ctx, vchan, @@ -287,14 +304,23 @@ int hab_mem_unexport(struct uhab_context *ctx, { int ret = 0, found = 0; struct export_desc *exp, *tmp; + struct virtual_channel *vchan; if (!ctx || !param) return -EINVAL; + /* refcnt on the access */ + vchan = hab_get_vchan_fromvcid(param->vcid, ctx); + if (!vchan || !vchan->pchan) { + ret = -ENODEV; + goto err_novchan; + } + write_lock(&ctx->exp_lock); list_for_each_entry_safe(exp, tmp, &ctx->exp_whse, node) { - if ((param->exportid == exp->export_id) && - (param->vcid == exp->vcid_local)) { + if (param->exportid == exp->export_id && + param->vcid == exp->vcid_local) { + /* same vchan guarantees the pchan for idr */ list_del(&exp->node); found = 1; break; @@ -302,15 +328,22 @@ int hab_mem_unexport(struct uhab_context *ctx, } write_unlock(&ctx->exp_lock); - if (!found) - return -EINVAL; + if (!found) { + ret = -EINVAL; + goto err_novchan; + } ret = habmem_hyp_revoke(exp->payload, exp->payload_count); if (ret) { pr_err("Error found in revoke grant with ret %d", ret); - return ret; + goto err_novchan; } habmem_remove_export(exp); + +err_novchan: + if (vchan) + hab_vchan_put(vchan); + return ret; } @@ -320,14 +353,24 @@ int hab_mem_import(struct uhab_context *ctx, { int ret = 0, found = 0; struct export_desc *exp = NULL; + struct virtual_channel *vchan; if (!ctx || !param) return -EINVAL; + vchan = hab_get_vchan_fromvcid(param->vcid, ctx); + if (!vchan || !vchan->pchan) { + ret = -ENODEV; + goto err_imp; + } + spin_lock_bh(&ctx->imp_lock); list_for_each_entry(exp, &ctx->imp_whse, node) { if ((exp->export_id == param->exportid) && (param->vcid == exp->vcid_remote)) { + /* only allow import on the vchan recevied from + * remote + */ found = 1; break; } @@ -338,27 +381,24 @@ int hab_mem_import(struct uhab_context *ctx, pr_err("Fail to get export descriptor from export id %d\n", param->exportid); ret = -ENODEV; - return ret; + goto err_imp; } - pr_debug("call map id: %d pcnt %d remote_dom %d 1st_ref:0x%X\n", - exp->export_id, exp->payload_count, exp->domid_local, - *((uint32_t *)exp->payload)); - ret = habmem_imp_hyp_map(ctx->import_ctx, param, exp, kernel); if (ret) { pr_err("Import fail ret:%d pcnt:%d rem:%d 1st_ref:0x%X\n", ret, exp->payload_count, exp->domid_local, *((uint32_t *)exp->payload)); - return ret; + goto err_imp; } exp->import_index = param->index; exp->kva = kernel ? (void *)param->kva : NULL; - pr_debug("import index %llx, kva or fd %llx, kernel %d\n", - exp->import_index, param->kva, kernel); +err_imp: + if (vchan) + hab_vchan_put(vchan); return ret; } @@ -369,20 +409,26 @@ int hab_mem_unimport(struct uhab_context *ctx, { int ret = 0, found = 0; struct export_desc *exp = NULL, *exp_tmp; + struct virtual_channel *vchan; if (!ctx || !param) return -EINVAL; + vchan = hab_get_vchan_fromvcid(param->vcid, ctx); + if (!vchan || !vchan->pchan) { + if (vchan) + hab_vchan_put(vchan); + return -ENODEV; + } + spin_lock_bh(&ctx->imp_lock); list_for_each_entry_safe(exp, exp_tmp, &ctx->imp_whse, node) { - if ((exp->export_id == param->exportid) && - (param->vcid == exp->vcid_remote)) { + if (exp->export_id == param->exportid && + param->vcid == exp->vcid_remote) { + /* same vchan is expected here */ list_del(&exp->node); ctx->import_total--; found = 1; - - pr_debug("found id:%d payload cnt:%d kernel:%d\n", - exp->export_id, exp->payload_count, kernel); break; } } @@ -391,7 +437,7 @@ int hab_mem_unimport(struct uhab_context *ctx, if (!found) ret = -EINVAL; else { - ret = habmm_imp_hyp_unmap(ctx->import_ctx, exp); + ret = habmm_imp_hyp_unmap(ctx->import_ctx, exp, kernel); if (ret) { pr_err("unmap fail id:%d pcnt:%d vcid:%d\n", exp->export_id, exp->payload_count, exp->vcid_remote); @@ -400,5 +446,8 @@ int hab_mem_unimport(struct uhab_context *ctx, kfree(exp); } + if (vchan) + hab_vchan_put(vchan); + return ret; } diff --git a/drivers/soc/qcom/hab/hab_msg.c b/drivers/soc/qcom/hab/hab_msg.c index d904cdee838c..9d5ee134c94e 100644 --- a/drivers/soc/qcom/hab/hab_msg.c +++ b/drivers/soc/qcom/hab/hab_msg.c @@ -78,13 +78,14 @@ hab_msg_dequeue(struct virtual_channel *vchan, struct hab_message **msg, } else { pr_err("rcv buffer too small %d < %zd\n", *rsize, message->sizebytes); - *rsize = 0; + *rsize = message->sizebytes; message = NULL; - ret = -EINVAL; + ret = -EOVERFLOW; /* come back again */ } } spin_unlock_bh(&vchan->rx_lock); } else + /* no message received, retain the original status */ *rsize = 0; *msg = message; @@ -142,7 +143,7 @@ static int hab_receive_create_export_ack(struct physical_channel *pchan, return -ENOMEM; if (sizeof(ack_recvd->ack) != sizebytes) - pr_err("exp ack size %lu is not as arrived %zu\n", + pr_err("exp ack size %zu is not as arrived %zu\n", sizeof(ack_recvd->ack), sizebytes); if (physical_channel_read(pchan, @@ -150,11 +151,6 @@ static int hab_receive_create_export_ack(struct physical_channel *pchan, sizebytes) != sizebytes) return -EIO; - pr_debug("receive export id %d, local vc %X, vd remote %X\n", - ack_recvd->ack.export_id, - ack_recvd->ack.vcid_local, - ack_recvd->ack.vcid_remote); - spin_lock_bh(&ctx->expq_lock); list_add_tail(&ack_recvd->node, &ctx->exp_rxq); spin_unlock_bh(&ctx->expq_lock); @@ -162,10 +158,21 @@ static int hab_receive_create_export_ack(struct physical_channel *pchan, return 0; } -void hab_msg_recv(struct physical_channel *pchan, +static void hab_msg_drop(struct physical_channel *pchan, size_t sizebytes) +{ + uint8_t *data = NULL; + + data = kmalloc(sizebytes, GFP_ATOMIC); + if (data == NULL) + return; + physical_channel_read(pchan, data, sizebytes); + kfree(data); +} + +int hab_msg_recv(struct physical_channel *pchan, struct hab_header *header) { - int ret; + int ret = 0; struct hab_message *message; struct hab_device *dev = pchan->habdev; size_t sizebytes = HAB_HEADER_GET_SIZE(*header); @@ -179,7 +186,8 @@ void hab_msg_recv(struct physical_channel *pchan, /* get the local virtual channel if it isn't an open message */ if (payload_type != HAB_PAYLOAD_TYPE_INIT && payload_type != HAB_PAYLOAD_TYPE_INIT_ACK && - payload_type != HAB_PAYLOAD_TYPE_ACK) { + payload_type != HAB_PAYLOAD_TYPE_INIT_DONE && + payload_type != HAB_PAYLOAD_TYPE_INIT_CANCEL) { /* sanity check the received message */ if (payload_type >= HAB_PAYLOAD_TYPE_MAX || @@ -189,29 +197,42 @@ void hab_msg_recv(struct physical_channel *pchan, payload_type, vchan_id, sizebytes, session_id); } + /* + * need both vcid and session_id to be accurate. + * this is from pchan instead of ctx + */ vchan = hab_vchan_get(pchan, header); if (!vchan) { - pr_debug("vchan is not found, payload type %d, vchan id %x, sizebytes %zx, session %d\n", + pr_info("vchan is not found, payload type %d, vchan id %x, sizebytes %zx, session %d\n", payload_type, vchan_id, sizebytes, session_id); - if (sizebytes) - pr_err("message is dropped\n"); - - return; + if (sizebytes) { + hab_msg_drop(pchan, sizebytes); + pr_err("message %d dropped no vchan, session id %d\n", + payload_type, session_id); + } + return -EINVAL; } else if (vchan->otherend_closed) { hab_vchan_put(vchan); - pr_debug("vchan remote is closed, payload type %d, vchan id %x, sizebytes %zx, session %d\n", + pr_info("vchan remote is closed payload type %d, vchan id %x, sizebytes %zx, session %d\n", payload_type, vchan_id, sizebytes, session_id); - - if (sizebytes) - pr_err("message is dropped\n"); - - return; + if (sizebytes) { + hab_msg_drop(pchan, sizebytes); + pr_err("message %d dropped remote close, session id %d\n", + payload_type, session_id); + } + return -ENODEV; } } else { if (sizebytes != sizeof(struct hab_open_send_data)) { - pr_err("Invalid open request received, payload type %d, vchan id %x, sizebytes %zx, session %d\n", + pr_err("Invalid open request received type %d, vcid %x, szbytes %zx, session %d\n", payload_type, vchan_id, sizebytes, session_id); + if (sizebytes) { + hab_msg_drop(pchan, sizebytes); + pr_err("message %d dropped unknown reason, session id %d\n", + payload_type, session_id); + } + return -ENODEV; } } @@ -226,7 +247,7 @@ void hab_msg_recv(struct physical_channel *pchan, case HAB_PAYLOAD_TYPE_INIT: case HAB_PAYLOAD_TYPE_INIT_ACK: - case HAB_PAYLOAD_TYPE_ACK: + case HAB_PAYLOAD_TYPE_INIT_DONE: ret = hab_open_request_add(pchan, sizebytes, payload_type); if (ret) { pr_err("open request add failed, ret %d, payload type %d, sizebytes %zx\n", @@ -236,6 +257,16 @@ void hab_msg_recv(struct physical_channel *pchan, wake_up_interruptible(&dev->openq); break; + case HAB_PAYLOAD_TYPE_INIT_CANCEL: + pr_info("remote open cancel header vcid %X session %d local %d remote %d\n", + vchan_id, session_id, pchan->vmid_local, + pchan->vmid_remote); + ret = hab_open_receive_cancel(pchan, sizebytes); + if (ret) + pr_err("open cancel handling failed ret %d vcid %X session %d\n", + ret, vchan_id, session_id); + break; + case HAB_PAYLOAD_TYPE_EXPORT: exp_desc = kzalloc(sizebytes, GFP_ATOMIC); if (!exp_desc) @@ -243,7 +274,10 @@ void hab_msg_recv(struct physical_channel *pchan, if (physical_channel_read(pchan, exp_desc, sizebytes) != sizebytes) { - vfree(exp_desc); + pr_err("corrupted exp expect %zd bytes vcid %X remote %X open %d!\n", + sizebytes, vchan->id, + vchan->otherend_id, vchan->session_id); + kfree(exp_desc); break; } @@ -265,36 +299,33 @@ void hab_msg_recv(struct physical_channel *pchan, case HAB_PAYLOAD_TYPE_CLOSE: /* remote request close */ - pr_debug("remote side request close\n"); - pr_debug(" vchan id %X, other end %X, session %d\n", - vchan->id, vchan->otherend_id, session_id); + pr_info("remote request close vcid %pK %X other id %X session %d refcnt %d\n", + vchan, vchan->id, vchan->otherend_id, + session_id, get_refcnt(vchan->refcount)); hab_vchan_stop(vchan); break; case HAB_PAYLOAD_TYPE_PROFILE: do_gettimeofday(&tv); - /* pull down the incoming data */ message = hab_msg_alloc(pchan, sizebytes); - if (!message) { - pr_err("msg alloc failed\n"); - break; + if (!message) + pr_err("failed to allocate msg Arrived msg will be lost\n"); + else { + struct habmm_xing_vm_stat *pstat = + (struct habmm_xing_vm_stat *)message->data; + pstat->rx_sec = tv.tv_sec; + pstat->rx_usec = tv.tv_usec; + hab_msg_queue(vchan, message); } - - ((uint64_t *)message->data)[2] = tv.tv_sec; - ((uint64_t *)message->data)[3] = tv.tv_usec; - hab_msg_queue(vchan, message); break; default: - pr_err("unknown msg is received\n"); - pr_err("payload type %d, vchan id %x\n", - payload_type, vchan_id); - pr_err("sizebytes %zx, session %d\n", - sizebytes, session_id); - + pr_err("unknown msg received, payload type %d, vchan id %x, sizebytes %zx, session %d\n", + payload_type, vchan_id, sizebytes, session_id); break; } if (vchan) hab_vchan_put(vchan); + return ret; } diff --git a/drivers/soc/qcom/hab/hab_open.c b/drivers/soc/qcom/hab/hab_open.c index 35f3281604e2..f740a43c1973 100644 --- a/drivers/soc/qcom/hab/hab_open.c +++ b/drivers/soc/qcom/hab/hab_open.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -12,6 +12,8 @@ */ #include "hab.h" +#define HAB_OPEN_REQ_EXPIRE_TIME_S (3600*10) + void hab_open_request_init(struct hab_open_request *request, int type, struct physical_channel *pchan, @@ -21,57 +23,55 @@ void hab_open_request_init(struct hab_open_request *request, { request->type = type; request->pchan = pchan; - request->vchan_id = vchan_id; - request->sub_id = sub_id; - request->open_id = open_id; + request->xdata.vchan_id = vchan_id; + request->xdata.sub_id = sub_id; + request->xdata.open_id = open_id; } int hab_open_request_send(struct hab_open_request *request) { struct hab_header header = HAB_HEADER_INITIALIZER; - struct hab_open_send_data data; HAB_HEADER_SET_SIZE(header, sizeof(struct hab_open_send_data)); HAB_HEADER_SET_TYPE(header, request->type); - data.vchan_id = request->vchan_id; - data.open_id = request->open_id; - data.sub_id = request->sub_id; - - return physical_channel_send(request->pchan, &header, &data); + return physical_channel_send(request->pchan, &header, &request->xdata); } +/* called when remote sends in open-request */ int hab_open_request_add(struct physical_channel *pchan, size_t sizebytes, int request_type) { struct hab_open_node *node; struct hab_device *dev = pchan->habdev; - struct hab_open_send_data data; struct hab_open_request *request; + struct timeval tv; node = kzalloc(sizeof(*node), GFP_ATOMIC); if (!node) return -ENOMEM; - if (physical_channel_read(pchan, &data, sizebytes) != sizebytes) + request = &node->request; + if (physical_channel_read(pchan, &request->xdata, sizebytes) + != sizebytes) return -EIO; - request = &node->request; - request->type = request_type; - request->pchan = pchan; - request->vchan_id = data.vchan_id; - request->sub_id = data.sub_id; - request->open_id = data.open_id; - node->age = 0; + request->type = request_type; + request->pchan = pchan; + + do_gettimeofday(&tv); + node->age = tv.tv_sec + HAB_OPEN_REQ_EXPIRE_TIME_S + + tv.tv_usec/1000000; hab_pchan_get(pchan); spin_lock_bh(&dev->openlock); list_add_tail(&node->node, &dev->openq_list); + dev->openq_cnt++; spin_unlock_bh(&dev->openlock); - return 0; } +/* local only */ static int hab_open_request_find(struct uhab_context *ctx, struct hab_device *dev, struct hab_open_request *listen, @@ -79,6 +79,7 @@ static int hab_open_request_find(struct uhab_context *ctx, { struct hab_open_node *node, *tmp; struct hab_open_request *request; + struct timeval tv; int ret = 0; if (ctx->closing || @@ -91,21 +92,27 @@ static int hab_open_request_find(struct uhab_context *ctx, if (list_empty(&dev->openq_list)) goto done; + do_gettimeofday(&tv); + list_for_each_entry_safe(node, tmp, &dev->openq_list, node) { request = (struct hab_open_request *)node; - if (request->type == listen->type && - (request->sub_id == listen->sub_id) && - (!listen->open_id || - request->open_id == listen->open_id) && + if ((request->type == listen->type || + request->type == HAB_PAYLOAD_TYPE_INIT_CANCEL) && + (request->xdata.sub_id == listen->xdata.sub_id) && + (!listen->xdata.open_id || + request->xdata.open_id == listen->xdata.open_id) && (!listen->pchan || request->pchan == listen->pchan)) { list_del(&node->node); + dev->openq_cnt--; *recv_request = request; ret = 1; break; } - node->age++; - if (node->age > Q_AGE_THRESHOLD) { + if (node->age < (int64_t)tv.tv_sec + tv.tv_usec/1000000) { + pr_warn("open request type %d sub %d open %d\n", + request->type, request->xdata.sub_id, + request->xdata.sub_id); list_del(&node->node); hab_open_request_free(request); } @@ -121,7 +128,8 @@ void hab_open_request_free(struct hab_open_request *request) if (request) { hab_pchan_put(request->pchan); kfree(request); - } + } else + pr_err("empty request found\n"); } int hab_open_listen(struct uhab_context *ctx, @@ -132,22 +140,153 @@ int hab_open_listen(struct uhab_context *ctx, { int ret = 0; - if (!ctx || !listen || !recv_request) + if (!ctx || !listen || !recv_request) { + pr_err("listen failed ctx %pK listen %pK request %pK\n", + ctx, listen, recv_request); return -EINVAL; + } *recv_request = NULL; - if (ms_timeout > 0) { + if (ms_timeout > 0) { /* be case */ + ms_timeout = msecs_to_jiffies(ms_timeout); ret = wait_event_interruptible_timeout(dev->openq, hab_open_request_find(ctx, dev, listen, recv_request), ms_timeout); - if (!ret || (-ERESTARTSYS == ret)) - ret = -EAGAIN; - else if (ret > 0) - ret = 0; - } else { + if (!ret || (-ERESTARTSYS == ret)) { + pr_warn("something failed in open listen ret %d\n", + ret); + ret = -EAGAIN; /* condition not met */ + } else if (ret > 0) + ret = 0; /* condition met */ + } else { /* fe case */ ret = wait_event_interruptible(dev->openq, hab_open_request_find(ctx, dev, listen, recv_request)); + if (ctx->closing) { + pr_warn("local closing during open ret %d\n", ret); + ret = -ENODEV; + } else if (-ERESTARTSYS == ret) { + pr_warn("local interrupted ret %d\n", ret); + ret = -EINTR; + } + } + + return ret; +} + +/* called when receives remote's cancel init from FE or init-ack from BE */ +int hab_open_receive_cancel(struct physical_channel *pchan, + size_t sizebytes) +{ + struct hab_device *dev = pchan->habdev; + struct hab_open_send_data data; + struct hab_open_request *request; + struct hab_open_node *node, *tmp; + int bfound = 0; + struct timeval tv; + + if (physical_channel_read(pchan, &data, sizebytes) != sizebytes) + return -EIO; + + spin_lock_bh(&dev->openlock); + list_for_each_entry_safe(node, tmp, &dev->openq_list, node) { + request = &node->request; + /* check if open request has been serviced or not */ + if ((request->type == HAB_PAYLOAD_TYPE_INIT || + request->type == HAB_PAYLOAD_TYPE_INIT_ACK) && + (request->xdata.sub_id == data.sub_id) && + (request->xdata.open_id == data.open_id) && + (request->xdata.vchan_id == data.vchan_id)) { + list_del(&node->node); + dev->openq_cnt--; + pr_info("open cancelled on pchan %s vcid %x subid %d openid %d\n", + pchan->name, data.vchan_id, + data.sub_id, data.open_id); + /* found un-serviced open request, delete it */ + bfound = 1; + break; + } + } + spin_unlock_bh(&dev->openlock); + + if (!bfound) { + pr_info("init waiting is in-flight. vcid %x sub %d open %d\n", + data.vchan_id, data.sub_id, data.open_id); + /* add cancel to the openq to let the waiting open bail out */ + node = kzalloc(sizeof(*node), GFP_ATOMIC); + if (!node) + return -ENOMEM; + + request = &node->request; + request->type = HAB_PAYLOAD_TYPE_INIT_CANCEL; + request->pchan = pchan; + request->xdata.vchan_id = data.vchan_id; + request->xdata.sub_id = data.sub_id; + request->xdata.open_id = data.open_id; + + do_gettimeofday(&tv); + node->age = tv.tv_sec + HAB_OPEN_REQ_EXPIRE_TIME_S + + tv.tv_usec/1000000; + /* put when this node is handled in open path */ + hab_pchan_get(pchan); + + spin_lock_bh(&dev->openlock); + list_add_tail(&node->node, &dev->openq_list); + dev->openq_cnt++; + spin_unlock_bh(&dev->openlock); + + wake_up_interruptible(&dev->openq); + } + + return 0; +} + +/* calls locally to send cancel pending open to remote */ +int hab_open_cancel_notify(struct hab_open_request *request) +{ + struct hab_header header = HAB_HEADER_INITIALIZER; + + HAB_HEADER_SET_SIZE(header, sizeof(struct hab_open_send_data)); + HAB_HEADER_SET_TYPE(header, HAB_PAYLOAD_TYPE_INIT_CANCEL); + + return physical_channel_send(request->pchan, &header, &request->xdata); +} + +int hab_open_pending_enter(struct uhab_context *ctx, + struct physical_channel *pchan, + struct hab_open_node *pending) +{ + write_lock(&ctx->ctx_lock); + list_add_tail(&pending->node, &ctx->pending_open); + ctx->pending_cnt++; + write_unlock(&ctx->ctx_lock); + + return 0; +} + +int hab_open_pending_exit(struct uhab_context *ctx, + struct physical_channel *pchan, + struct hab_open_node *pending) +{ + struct hab_open_node *node, *tmp; + int ret = -ENOENT; + + write_lock(&ctx->ctx_lock); + list_for_each_entry_safe(node, tmp, &ctx->pending_open, node) { + if ((node->request.type == pending->request.type) && + (node->request.pchan + == pending->request.pchan) && + (node->request.xdata.vchan_id + == pending->request.xdata.vchan_id) && + (node->request.xdata.sub_id + == pending->request.xdata.sub_id) && + (node->request.xdata.open_id + == pending->request.xdata.open_id)) { + list_del(&node->node); + ctx->pending_cnt--; + ret = 0; + } } + write_unlock(&ctx->ctx_lock); return ret; } diff --git a/drivers/soc/qcom/hab/hab_parser.c b/drivers/soc/qcom/hab/hab_parser.c index da0a4a3830a7..c332587e2b47 100644 --- a/drivers/soc/qcom/hab/hab_parser.c +++ b/drivers/soc/qcom/hab/hab_parser.c @@ -30,7 +30,7 @@ static int fill_vmid_mmid_tbl(struct vmid_mmid_desc *tbl, int32_t vm_start, for (j = mmid_start; j < mmid_start + mmid_range; j++) { /* sanity check */ if (tbl[i].mmid[j] != HABCFG_VMID_INVALID) { - pr_err("overwrite previous setting, i %d, j %d, be %d\n", + pr_err("overwrite previous setting vmid %d, mmid %d, be %d\n", i, j, tbl[i].is_listener[j]); } tbl[i].mmid[j] = j; @@ -43,28 +43,23 @@ static int fill_vmid_mmid_tbl(struct vmid_mmid_desc *tbl, int32_t vm_start, void dump_settings(struct local_vmid *settings) { - int i, j; - pr_debug("self vmid is %d\n", settings->self); - for (i = 0; i < HABCFG_VMID_MAX; i++) { - pr_debug("remote vmid %d\n", - settings->vmid_mmid_list[i].vmid); - for (j = 0; j <= HABCFG_MMID_AREA_MAX; j++) { - pr_debug("mmid %d, is_be %d\n", - settings->vmid_mmid_list[i].mmid[j], - settings->vmid_mmid_list[i].is_listener[j]); - } - } } int fill_default_gvm_settings(struct local_vmid *settings, int vmid_local, - int mmid_start, int mmid_end) { + int mmid_start, int mmid_end) +{ + int32_t be = HABCFG_BE_FALSE; + int32_t range = 1; + int32_t vmremote = 0; /* default to host[0] as local is guest[2] */ + settings->self = vmid_local; /* default gvm always talks to host as vm0 */ - return fill_vmid_mmid_tbl(settings->vmid_mmid_list, 0, 1, - mmid_start/100, (mmid_end-mmid_start)/100+1, HABCFG_BE_FALSE); + return fill_vmid_mmid_tbl(settings->vmid_mmid_list, vmremote, range, + mmid_start/100, (mmid_end-mmid_start)/100+1, be); } +/* device tree based parser */ static int hab_parse_dt(struct local_vmid *settings) { int result, i; @@ -151,6 +146,10 @@ static int hab_parse_dt(struct local_vmid *settings) return 0; } +/* + * 0: successful + * negative: various failure core + */ int hab_parse(struct local_vmid *settings) { int ret; diff --git a/drivers/soc/qcom/hab/hab_pchan.c b/drivers/soc/qcom/hab/hab_pchan.c index 36bc29b7bd0c..8a9a6dfd1e0c 100644 --- a/drivers/soc/qcom/hab/hab_pchan.c +++ b/drivers/soc/qcom/hab/hab_pchan.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -35,10 +35,10 @@ hab_pchan_alloc(struct hab_device *habdev, int otherend_id) rwlock_init(&pchan->vchans_lock); spin_lock_init(&pchan->rxbuf_lock); - mutex_lock(&habdev->pchan_lock); + spin_lock_bh(&habdev->pchan_lock); list_add_tail(&pchan->node, &habdev->pchannels); habdev->pchan_cnt++; - mutex_unlock(&habdev->pchan_lock); + spin_unlock_bh(&habdev->pchan_lock); return pchan; } @@ -47,11 +47,26 @@ static void hab_pchan_free(struct kref *ref) { struct physical_channel *pchan = container_of(ref, struct physical_channel, refcount); + struct virtual_channel *vchan; - mutex_lock(&pchan->habdev->pchan_lock); + pr_debug("pchan %s refcnt %d\n", pchan->name, + get_refcnt(pchan->refcount)); + + spin_lock_bh(&pchan->habdev->pchan_lock); list_del(&pchan->node); pchan->habdev->pchan_cnt--; - mutex_unlock(&pchan->habdev->pchan_lock); + spin_unlock_bh(&pchan->habdev->pchan_lock); + + /* check vchan leaking */ + read_lock(&pchan->vchans_lock); + list_for_each_entry(vchan, &pchan->vchannels, pnode) { + /* no logging on the owner. it might have been gone */ + pr_warn("leaking vchan id %X remote %X refcnt %d\n", + vchan->id, vchan->otherend_id, + get_refcnt(vchan->refcount)); + } + read_unlock(&pchan->vchans_lock); + kfree(pchan->hyp_data); kfree(pchan); } @@ -61,7 +76,7 @@ hab_pchan_find_domid(struct hab_device *dev, int dom_id) { struct physical_channel *pchan; - mutex_lock(&dev->pchan_lock); + spin_lock_bh(&dev->pchan_lock); list_for_each_entry(pchan, &dev->pchannels, node) if (pchan->dom_id == dom_id || dom_id == HABCFG_VMID_DONT_CARE) break; @@ -75,7 +90,7 @@ hab_pchan_find_domid(struct hab_device *dev, int dom_id) if (pchan && !kref_get_unless_zero(&pchan->refcount)) pchan = NULL; - mutex_unlock(&dev->pchan_lock); + spin_unlock_bh(&dev->pchan_lock); return pchan; } diff --git a/drivers/soc/qcom/hab/hab_qvm.c b/drivers/soc/qcom/hab/hab_qvm.c index 9aa41320a33f..129d1deeb2f0 100644 --- a/drivers/soc/qcom/hab/hab_qvm.c +++ b/drivers/soc/qcom/hab/hab_qvm.c @@ -71,14 +71,14 @@ static struct qvm_plugin_info { static irqreturn_t shm_irq_handler(int irq, void *_pchan) { irqreturn_t rc = IRQ_NONE; - struct physical_channel *pchan = _pchan; + struct physical_channel *pchan = (struct physical_channel *) _pchan; struct qvm_channel *dev = (struct qvm_channel *) (pchan ? pchan->hyp_data : NULL); if (dev && dev->guest_ctrl) { int status = dev->guest_ctrl->status; - if (status & dev->idx) { + if (status & 0xffff) {/*source bitmask indicator*/ rc = IRQ_HANDLED; tasklet_schedule(&dev->task); } @@ -95,13 +95,14 @@ static uint64_t get_guest_factory_paddr(struct qvm_channel *dev, int i; pr_debug("name = %s, factory paddr = 0x%lx, irq %d, pages %d\n", - name, factory_addr, irq, pages); + name, factory_addr, irq, pages); dev->guest_factory = (struct guest_shm_factory *)factory_addr; if (dev->guest_factory->signature != GUEST_SHM_SIGNATURE) { pr_err("signature error: %ld != %llu, factory addr %lx\n", GUEST_SHM_SIGNATURE, dev->guest_factory->signature, factory_addr); + iounmap(dev->guest_factory); return 0; } @@ -120,6 +121,7 @@ static uint64_t get_guest_factory_paddr(struct qvm_channel *dev, /* See if we successfully created/attached to the region. */ if (dev->guest_factory->status != GSS_OK) { pr_err("create failed: %d\n", dev->guest_factory->status); + iounmap(dev->guest_factory); return 0; } @@ -180,6 +182,7 @@ int habhyp_commdev_alloc(void **commdev, int is_be, char *name, { struct qvm_channel *dev = NULL; struct qvm_plugin_info *qvm_priv = hab_driver.hyp_priv; + uint64_t paddr; struct physical_channel **pchan = (struct physical_channel **)commdev; int ret = 0, coid = 0, channel = 0; char *shmdata; @@ -187,7 +190,6 @@ int habhyp_commdev_alloc(void **commdev, int is_be, char *name, hab_pipe_calc_required_bytes(PIPE_SHMEM_SIZE); uint32_t pipe_alloc_pages = (pipe_alloc_size + PAGE_SIZE - 1) / PAGE_SIZE; - uint64_t paddr; int temp; int total_pages; struct page **pages; @@ -196,8 +198,10 @@ int habhyp_commdev_alloc(void **commdev, int is_be, char *name, pipe_alloc_size); dev = kzalloc(sizeof(*dev), GFP_KERNEL); - if (!dev) - return -ENOMEM; + if (!dev) { + ret = -ENOMEM; + goto err; + } spin_lock_init(&dev->io_lock); @@ -208,7 +212,7 @@ int habhyp_commdev_alloc(void **commdev, int is_be, char *name, pipe_alloc_pages); qvm_priv->curr++; if (qvm_priv->curr > qvm_priv->probe_cnt) { - pr_err("factory setting %d overflow probed cnt %d\n", + pr_err("pchan guest factory setting %d overflow probed cnt %d\n", qvm_priv->curr, qvm_priv->probe_cnt); ret = -1; goto err; @@ -261,17 +265,18 @@ int habhyp_commdev_alloc(void **commdev, int is_be, char *name, dev->coid = coid; ret = create_dispatcher(*pchan); - if (ret) + if (ret < 0) goto err; return ret; err: + pr_err("habhyp_commdev_alloc failed\n"); + kfree(dev); if (*pchan) hab_pchan_put(*pchan); - pr_err("habhyp_commdev_alloc failed: %d\n", ret); return ret; } @@ -280,6 +285,13 @@ int habhyp_commdev_dealloc(void *commdev) struct physical_channel *pchan = (struct physical_channel *)commdev; struct qvm_channel *dev = pchan->hyp_data; + dev->guest_ctrl->detach = 0; + + if (get_refcnt(pchan->refcount) > 1) { + pr_warn("potential leak pchan %s vchans %d refcnt %d\n", + pchan->name, pchan->vcnt, + get_refcnt(pchan->refcount)); + } kfree(dev); hab_pchan_put(pchan); @@ -302,25 +314,13 @@ int hab_hypervisor_register(void) void hab_hypervisor_unregister(void) { - int status, i; - - for (i = 0; i < hab_driver.ndevices; i++) { - struct hab_device *dev = &hab_driver.devp[i]; - struct physical_channel *pchan; - - list_for_each_entry(pchan, &dev->pchannels, node) { - status = habhyp_commdev_dealloc(pchan); - if (status) { - pr_err("failed to free pchan %pK, i %d, ret %d\n", - pchan, i, status); - } - } - } + hab_hypervisor_unregister_common(); qvm_priv_info.probe_cnt = 0; qvm_priv_info.curr = 0; } +/* this happens before hypervisor register */ static int hab_shmem_probe(struct platform_device *pdev) { int irq = 0; @@ -373,19 +373,6 @@ static int hab_shmem_remove(struct platform_device *pdev) static void hab_shmem_shutdown(struct platform_device *pdev) { - int i; - struct qvm_channel *dev; - struct physical_channel *pchan; - struct hab_device *hab_dev; - - for (i = 0; i < hab_driver.ndevices; i++) { - hab_dev = &hab_driver.devp[i]; - pr_debug("detaching %s\n", hab_dev->name); - list_for_each_entry(pchan, &hab_dev->pchannels, node) { - dev = (struct qvm_channel *)pchan->hyp_data; - dev->guest_ctrl->detach = 0; - } - } } static const struct of_device_id hab_shmem_match_table[] = { diff --git a/drivers/soc/qcom/hab/hab_qvm.h b/drivers/soc/qcom/hab/hab_qvm.h index b483f4c21331..fe7cb0bbda0a 100644 --- a/drivers/soc/qcom/hab/hab_qvm.h +++ b/drivers/soc/qcom/hab/hab_qvm.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -36,6 +36,7 @@ struct qvm_channel { int channel; int coid; + /* Guest VM */ unsigned int guest_intr; unsigned int guest_iid; unsigned int factory_addr; diff --git a/drivers/soc/qcom/hab/hab_vchan.c b/drivers/soc/qcom/hab/hab_vchan.c index 2db4db8f321b..d127bcca19f8 100644 --- a/drivers/soc/qcom/hab/hab_vchan.c +++ b/drivers/soc/qcom/hab/hab_vchan.c @@ -13,7 +13,8 @@ #include "hab.h" struct virtual_channel * -hab_vchan_alloc(struct uhab_context *ctx, struct physical_channel *pchan) +hab_vchan_alloc(struct uhab_context *ctx, struct physical_channel *pchan, + int openid) { int id; struct virtual_channel *vchan; @@ -28,11 +29,13 @@ hab_vchan_alloc(struct uhab_context *ctx, struct physical_channel *pchan) /* This should be the first thing we do in this function */ idr_preload(GFP_KERNEL); spin_lock_bh(&pchan->vid_lock); - id = idr_alloc(&pchan->vchan_idr, vchan, 1, 256, GFP_NOWAIT); + id = idr_alloc(&pchan->vchan_idr, vchan, 1, + (HAB_VCID_ID_MASK >> HAB_VCID_ID_SHIFT) + 1, GFP_NOWAIT); spin_unlock_bh(&pchan->vid_lock); idr_preload_end(); - if (id < 0) { + if (id <= 0) { + pr_err("idr failed %d\n", id); kfree(vchan); return NULL; } @@ -40,8 +43,11 @@ hab_vchan_alloc(struct uhab_context *ctx, struct physical_channel *pchan) hab_pchan_get(pchan); vchan->pchan = pchan; + /* vchan need both vcid and openid to be properly located */ + vchan->session_id = openid; write_lock(&pchan->vchans_lock); list_add_tail(&vchan->pnode, &pchan->vchannels); + pchan->vcnt++; write_unlock(&pchan->vchans_lock); vchan->id = ((id << HAB_VCID_ID_SHIFT) & HAB_VCID_ID_MASK) | ((pchan->habdev->id << HAB_VCID_MMID_SHIFT) & @@ -53,7 +59,7 @@ hab_vchan_alloc(struct uhab_context *ctx, struct physical_channel *pchan) init_waitqueue_head(&vchan->rx_queue); kref_init(&vchan->refcount); - kref_init(&vchan->usagecnt); + vchan->otherend_closed = pchan->closed; hab_ctx_get(ctx); @@ -65,11 +71,9 @@ hab_vchan_alloc(struct uhab_context *ctx, struct physical_channel *pchan) static void hab_vchan_free(struct kref *ref) { - int found; struct virtual_channel *vchan = container_of(ref, struct virtual_channel, refcount); struct hab_message *message, *msg_tmp; - struct export_desc *exp, *exp_tmp; struct physical_channel *pchan = vchan->pchan; struct uhab_context *ctx = vchan->ctx; struct virtual_channel *vc, *vc_tmp; @@ -81,73 +85,84 @@ hab_vchan_free(struct kref *ref) } spin_unlock_bh(&vchan->rx_lock); - do { - found = 0; - write_lock(&ctx->exp_lock); - list_for_each_entry_safe(exp, exp_tmp, &ctx->exp_whse, node) { - if (exp->vcid_local == vchan->id) { - list_del(&exp->node); - found = 1; - break; - } - } - write_unlock(&ctx->exp_lock); - if (found) { - habmem_hyp_revoke(exp->payload, exp->payload_count); - habmem_remove_export(exp); - } - } while (found); - - do { - found = 0; - spin_lock_bh(&ctx->imp_lock); - list_for_each_entry_safe(exp, exp_tmp, &ctx->imp_whse, node) { - if (exp->vcid_remote == vchan->id) { - list_del(&exp->node); - found = 1; - break; - } - } - spin_unlock_bh(&ctx->imp_lock); - if (found) { - habmm_imp_hyp_unmap(ctx->import_ctx, exp); - ctx->import_total--; - kfree(exp); - } - } while (found); - - spin_lock_bh(&pchan->vid_lock); - idr_remove(&pchan->vchan_idr, HAB_VCID_GET_ID(vchan->id)); - spin_unlock_bh(&pchan->vid_lock); + /* the release vchan from ctx was done earlier in vchan close() */ + hab_ctx_put(ctx); /* now ctx is not needed from this vchan's view */ + vchan->ctx = NULL; + /* release vchan from pchan. no more msg for this vchan */ write_lock(&pchan->vchans_lock); list_for_each_entry_safe(vc, vc_tmp, &pchan->vchannels, pnode) { if (vchan == vc) { list_del(&vc->pnode); + /* the ref is held in case of pchan is freed */ + pchan->vcnt--; break; } } write_unlock(&pchan->vchans_lock); - hab_pchan_put(pchan); - hab_ctx_put(ctx); + /* release idr at the last so same idr will not be used early */ + spin_lock_bh(&pchan->vid_lock); + idr_remove(&pchan->vchan_idr, HAB_VCID_GET_ID(vchan->id)); + spin_unlock_bh(&pchan->vid_lock); + + hab_pchan_put(pchan); /* no more need for pchan from this vchan */ kfree(vchan); } +/* + * only for msg recv path to retrieve vchan from vcid and openid based on + * pchan's vchan list + */ struct virtual_channel* hab_vchan_get(struct physical_channel *pchan, struct hab_header *header) { struct virtual_channel *vchan; uint32_t vchan_id = HAB_HEADER_GET_ID(*header); uint32_t session_id = HAB_HEADER_GET_SESSION_ID(*header); + size_t sizebytes = HAB_HEADER_GET_SIZE(*header); + uint32_t payload_type = HAB_HEADER_GET_TYPE(*header); spin_lock_bh(&pchan->vid_lock); vchan = idr_find(&pchan->vchan_idr, HAB_VCID_GET_ID(vchan_id)); - if (vchan) - if ((vchan->session_id != session_id) || - (!kref_get_unless_zero(&vchan->refcount))) + if (vchan) { + if (vchan->session_id != session_id) + /* + * skipped if session is different even vcid + * is the same + */ + vchan = NULL; + else if (!vchan->otherend_id /*&& !vchan->session_id*/) { + /* + * not paired vchan can be fetched right after it is + * alloc'ed. so it has to be skipped during search + * for remote msg + */ + pr_warn("vcid %x is not paired yet session %d refcnt %d type %d sz %zd\n", + vchan->id, vchan->otherend_id, + get_refcnt(vchan->refcount), + payload_type, sizebytes); + vchan = NULL; + } else if (!kref_get_unless_zero(&vchan->refcount)) { + /* + * this happens when refcnt is already zero + * (put from other thread) or there is an actual error + */ + pr_err("failed to inc vcid %pK %x remote %x session %d refcnt %d header %x session %d type %d sz %zd\n", + vchan, vchan->id, vchan->otherend_id, + vchan->session_id, get_refcnt(vchan->refcount), + vchan_id, session_id, payload_type, sizebytes); + vchan = NULL; + } else if (vchan->otherend_closed || vchan->closed) { + pr_err("closed already remote %d local %d vcid %x remote %x session %d refcnt %d header %x session %d type %d sz %zd\n", + vchan->otherend_closed, vchan->closed, + vchan->id, vchan->otherend_id, + vchan->session_id, get_refcnt(vchan->refcount), + vchan_id, session_id, payload_type, sizebytes); vchan = NULL; + } + } spin_unlock_bh(&pchan->vid_lock); return vchan; @@ -158,6 +173,7 @@ void hab_vchan_stop(struct virtual_channel *vchan) if (vchan) { vchan->otherend_closed = 1; wake_up(&vchan->rx_queue); + wake_up_interruptible(&vchan->ctx->exp_wq); } } @@ -184,23 +200,36 @@ int hab_vchan_find_domid(struct virtual_channel *vchan) return vchan ? vchan->pchan->dom_id : -1; } -static void -hab_vchan_free_deferred(struct work_struct *work) -{ - struct virtual_channel *vchan = - container_of(work, struct virtual_channel, work); - - hab_vchan_free(&vchan->refcount); -} - -static void -hab_vchan_schedule_free(struct kref *ref) +/* this sould be only called once after refcnt is zero */ +static void hab_vchan_schedule_free(struct kref *ref) { - struct virtual_channel *vchan = + struct virtual_channel *vchanin = container_of(ref, struct virtual_channel, refcount); + struct uhab_context *ctx = vchanin->ctx; + struct virtual_channel *vchan, *tmp; + int bnotify = 0; + + /* + * similar logic is in ctx free. if ctx free runs first, + * this is skipped + */ + write_lock(&ctx->ctx_lock); + list_for_each_entry_safe(vchan, tmp, &ctx->vchannels, node) { + if (vchan == vchanin) { + pr_debug("vchan free refcnt = %d\n", + get_refcnt(vchan->refcount)); + ctx->vcnt--; + list_del(&vchan->node); + bnotify = 1; + break; + } + } + write_unlock(&ctx->ctx_lock); - INIT_WORK(&vchan->work, hab_vchan_free_deferred); - schedule_work(&vchan->work); + if (bnotify) + hab_vchan_stop_notify(vchan); + + hab_vchan_free(ref); } void hab_vchan_put(struct virtual_channel *vchan) @@ -210,17 +239,23 @@ void hab_vchan_put(struct virtual_channel *vchan) } int hab_vchan_query(struct uhab_context *ctx, int32_t vcid, uint64_t *ids, - char *names, size_t name_size, uint32_t flags) + char *names, size_t name_size, uint32_t flags) { struct virtual_channel *vchan = hab_get_vchan_fromvcid(vcid, ctx); + if (!vchan) + return -EINVAL; - if (!vchan || vchan->otherend_closed) + if (vchan->otherend_closed) { + hab_vchan_put(vchan); return -ENODEV; + } *ids = vchan->pchan->vmid_local | ((uint64_t)vchan->pchan->vmid_remote) << 32; names[0] = 0; names[name_size/2] = 0; + hab_vchan_put(vchan); + return 0; } diff --git a/drivers/soc/qcom/hab/khab.c b/drivers/soc/qcom/hab/khab.c index ba77e5e9cca2..c4acf12fd553 100644 --- a/drivers/soc/qcom/hab/khab.c +++ b/drivers/soc/qcom/hab/khab.c @@ -10,13 +10,14 @@ * GNU General Public License for more details. * */ -#include <linux/module.h> #include "hab.h" +#include <linux/module.h> int32_t habmm_socket_open(int32_t *handle, uint32_t mm_ip_id, uint32_t timeout, uint32_t flags) { - return hab_vchan_open(hab_driver.kctx, mm_ip_id, handle, flags); + return hab_vchan_open(hab_driver.kctx, mm_ip_id, handle, + timeout, flags); } EXPORT_SYMBOL(habmm_socket_open); @@ -55,6 +56,9 @@ int32_t habmm_socket_recv(int32_t handle, void *dst_buff, uint32_t *size_bytes, if (ret == 0 && msg) memcpy(dst_buff, msg->data, msg->sizebytes); + else if (ret && msg) + pr_warn("vcid %X recv failed %d but msg is still received %zd bytes\n", + handle, ret, msg->sizebytes); if (msg) hab_msg_free(msg); diff --git a/drivers/soc/qcom/hab/qvm_comm.c b/drivers/soc/qcom/hab/qvm_comm.c index 41e34be9ac21..04381232b26a 100644 --- a/drivers/soc/qcom/hab/qvm_comm.c +++ b/drivers/soc/qcom/hab/qvm_comm.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -43,7 +43,6 @@ int physical_channel_send(struct physical_channel *pchan, int sizebytes = HAB_HEADER_GET_SIZE(*header); struct qvm_channel *dev = (struct qvm_channel *)pchan->hyp_data; int total_size = sizeof(*header) + sizebytes; - struct timeval tv; if (total_size > dev->pipe_ep->tx_info.sh_buf->size) return -EINVAL; /* too much data for ring */ @@ -67,9 +66,13 @@ int physical_channel_send(struct physical_channel *pchan, } if (HAB_HEADER_GET_TYPE(*header) == HAB_PAYLOAD_TYPE_PROFILE) { + struct timeval tv; + struct habmm_xing_vm_stat *pstat = + (struct habmm_xing_vm_stat *)payload; + do_gettimeofday(&tv); - ((uint64_t *)payload)[0] = tv.tv_sec; - ((uint64_t *)payload)[1] = tv.tv_usec; + pstat->tx_sec = tv.tv_sec; + pstat->tx_usec = tv.tv_usec; } if (sizebytes) { @@ -102,7 +105,7 @@ void physical_channel_rx_dispatch(unsigned long data) break; /* no data available */ if (header.signature != HAB_HEAD_SIGNATURE) { - pr_err("HAB signature mismatch, expect %X, received %X, id_type_size %X, session %X, sequence %X\n", + pr_err("HAB signature mismatch expect %X received %X, id_type_size %X session %X sequence %X\n", HAB_HEAD_SIGNATURE, header.signature, header.id_type_size, header.session_id, diff --git a/drivers/soc/qcom/msm_bus/msm_bus_fabric_adhoc.c b/drivers/soc/qcom/msm_bus/msm_bus_fabric_adhoc.c index 7288c79de428..c41050adae5a 100644 --- a/drivers/soc/qcom/msm_bus/msm_bus_fabric_adhoc.c +++ b/drivers/soc/qcom/msm_bus/msm_bus_fabric_adhoc.c @@ -1038,10 +1038,8 @@ static struct device *msm_bus_device_init( bus_node = kzalloc(sizeof(struct msm_bus_node_device_type), GFP_KERNEL); if (!bus_node) { - MSM_BUS_ERR("%s:Bus node alloc failed\n", __func__); - kfree(bus_dev); - bus_dev = NULL; - goto exit_device_init; + ret = -ENOMEM; + goto err_device_init; } bus_dev = &bus_node->dev; device_initialize(bus_dev); @@ -1049,47 +1047,37 @@ static struct device *msm_bus_device_init( node_info = devm_kzalloc(bus_dev, sizeof(struct msm_bus_node_info_type), GFP_KERNEL); if (!node_info) { - MSM_BUS_ERR("%s:Bus node info alloc failed\n", __func__); - devm_kfree(bus_dev, bus_node); - kfree(bus_dev); - bus_dev = NULL; - goto exit_device_init; + ret = -ENOMEM; + goto err_put_device; } bus_node->node_info = node_info; bus_node->ap_owned = pdata->ap_owned; bus_dev->of_node = pdata->of_node; - if (msm_bus_copy_node_info(pdata, bus_dev) < 0) { - devm_kfree(bus_dev, bus_node); - devm_kfree(bus_dev, node_info); - kfree(bus_dev); - bus_dev = NULL; - goto exit_device_init; - } + ret = msm_bus_copy_node_info(pdata, bus_dev); + if (ret) + goto err_put_device; bus_dev->bus = &msm_bus_type; dev_set_name(bus_dev, bus_node->node_info->name); ret = device_add(bus_dev); - if (ret < 0) { + if (ret) { MSM_BUS_ERR("%s: Error registering device %d", __func__, pdata->node_info->id); - devm_kfree(bus_dev, bus_node); - devm_kfree(bus_dev, node_info->dev_connections); - devm_kfree(bus_dev, node_info->connections); - devm_kfree(bus_dev, node_info->black_connections); - devm_kfree(bus_dev, node_info->black_listed_connections); - devm_kfree(bus_dev, node_info); - kfree(bus_dev); - bus_dev = NULL; - goto exit_device_init; + goto err_put_device; } device_create_file(bus_dev, &dev_attr_bw); INIT_LIST_HEAD(&bus_node->devlist); - -exit_device_init: return bus_dev; + +err_put_device: + put_device(bus_dev); + bus_dev = NULL; + kfree(bus_node); +err_device_init: + return ERR_PTR(ret); } static int msm_bus_setup_dev_conn(struct device *bus_dev, void *data) @@ -1284,10 +1272,10 @@ static int msm_bus_device_probe(struct platform_device *pdev) node_dev = msm_bus_device_init(&pdata->info[i]); - if (!node_dev) { + if (IS_ERR(node_dev)) { MSM_BUS_ERR("%s: Error during dev init for %d", __func__, pdata->info[i].node_info->id); - ret = -ENXIO; + ret = PTR_ERR(node_dev); goto exit_device_probe; } diff --git a/drivers/soc/qcom/qdsp6v2/apr.c b/drivers/soc/qcom/qdsp6v2/apr.c index fefc348c0027..8cd86915be98 100644 --- a/drivers/soc/qcom/qdsp6v2/apr.c +++ b/drivers/soc/qcom/qdsp6v2/apr.c @@ -45,7 +45,8 @@ static void *apr_pkt_ctx; static wait_queue_head_t dsp_wait; static wait_queue_head_t modem_wait; static bool is_modem_up; -static bool is_initial_boot; +static bool is_initial_modem_boot; +static bool is_initial_adsp_boot; /* Subsystem restart: QDSP6 data, functions */ static struct workqueue_struct *apr_reset_workqueue; static void apr_reset_deregister(struct work_struct *work); @@ -909,21 +910,28 @@ static int apr_notifier_service_cb(struct notifier_block *this, * recovery notifications during initial boot * up since everything is expected to be down. */ - if (is_initial_boot) { - is_initial_boot = false; - break; - } - if (cb_data->domain == AUDIO_NOTIFIER_MODEM_DOMAIN) + if (cb_data->domain == AUDIO_NOTIFIER_MODEM_DOMAIN) { + if (is_initial_modem_boot) { + is_initial_modem_boot = false; + break; + } apr_modem_down(opcode); - else + } else { + if (is_initial_adsp_boot) { + is_initial_adsp_boot = false; + break; + } apr_adsp_down(opcode); + } break; case AUDIO_NOTIFIER_SERVICE_UP: - is_initial_boot = false; - if (cb_data->domain == AUDIO_NOTIFIER_MODEM_DOMAIN) + if (cb_data->domain == AUDIO_NOTIFIER_MODEM_DOMAIN) { + is_initial_modem_boot = false; apr_modem_up(); - else + } else { + is_initial_adsp_boot = false; apr_adsp_up(); + } break; default: break; @@ -965,7 +973,8 @@ static int __init apr_init(void) if (!apr_pkt_ctx) pr_err("%s: Unable to create ipc log context\n", __func__); - is_initial_boot = true; + is_initial_modem_boot = true; + is_initial_adsp_boot = true; subsys_notif_register("apr_adsp", AUDIO_NOTIFIER_ADSP_DOMAIN, &adsp_service_nb); subsys_notif_register("apr_modem", AUDIO_NOTIFIER_MODEM_DOMAIN, diff --git a/drivers/soc/qcom/subsystem_restart.c b/drivers/soc/qcom/subsystem_restart.c index ae249f382339..ea94456ccef8 100644 --- a/drivers/soc/qcom/subsystem_restart.c +++ b/drivers/soc/qcom/subsystem_restart.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2011-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -1083,7 +1083,7 @@ int subsystem_restart_dev(struct subsys_device *dev) { const char *name; - if (!get_device(&dev->dev)) + if ((!dev) || !get_device(&dev->dev)) return -ENODEV; if (!try_module_get(dev->owner)) { @@ -1177,11 +1177,21 @@ EXPORT_SYMBOL(subsystem_crashed); void subsys_set_crash_status(struct subsys_device *dev, enum crash_status crashed) { + if (!dev) { + pr_err("Invalid subsystem device\n"); + return; + } + dev->crashed = crashed; } enum crash_status subsys_get_crash_status(struct subsys_device *dev) { + if (!dev) { + pr_err("Invalid subsystem device\n"); + return CRASH_STATUS_NO_CRASH; + } + return dev->crashed; } diff --git a/drivers/staging/android/ion/ion_system_heap.c b/drivers/staging/android/ion/ion_system_heap.c index 2ad4cc7a4785..a2ead280ac4e 100644 --- a/drivers/staging/android/ion/ion_system_heap.c +++ b/drivers/staging/android/ion/ion_system_heap.c @@ -2,7 +2,7 @@ * drivers/staging/android/ion/ion_system_heap.c * * Copyright (C) 2011 Google, Inc. - * Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. + * Copyright (c) 2011-2018, The Linux Foundation. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -754,8 +754,10 @@ static void ion_system_heap_destroy_pools(struct ion_page_pool **pools) { int i; for (i = 0; i < num_orders; i++) - if (pools[i]) + if (pools[i]) { ion_page_pool_destroy(pools[i]); + pools[i] = NULL; + } } /** diff --git a/drivers/uio/msm_sharedmem/msm_sharedmem.c b/drivers/uio/msm_sharedmem/msm_sharedmem.c index b10c40b3e1fc..84623c9b41d3 100644 --- a/drivers/uio/msm_sharedmem/msm_sharedmem.c +++ b/drivers/uio/msm_sharedmem/msm_sharedmem.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2013-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -75,6 +75,24 @@ static int sharedmem_mmap(struct uio_info *info, struct vm_area_struct *vma) return result; } +static void free_shared_ram_perms(u32 client_id, phys_addr_t addr, u32 size) +{ + int ret; + u32 source_vmlist[2] = {VMID_HLOS, VMID_MSS_MSA}; + int dest_vmids[1] = {VMID_HLOS}; + int dest_perms[1] = {PERM_READ|PERM_WRITE|PERM_EXEC}; + + if (client_id != MPSS_RMTS_CLIENT_ID) + return; + + ret = hyp_assign_phys(addr, size, source_vmlist, 2, dest_vmids, + dest_perms, 1); + if (ret != 0) { + pr_err("hyp_assign_phys failed IPA=0x016%pa size=%u err=%d\n", + &addr, size, ret); + } +} + /* Setup the shared ram permissions. * This function currently supports the mpss client only. */ @@ -184,6 +202,17 @@ out: return ret; } +static void msm_sharedmem_shutdown(struct platform_device *pdev) +{ + struct uio_info *info = dev_get_drvdata(&pdev->dev); + + phys_addr_t shared_mem_addr = info->mem[0].addr; + u32 shared_mem_size = info->mem[0].size; + + free_shared_ram_perms(MPSS_RMTS_CLIENT_ID, shared_mem_addr, + shared_mem_size); +} + static int msm_sharedmem_remove(struct platform_device *pdev) { struct uio_info *info = dev_get_drvdata(&pdev->dev); @@ -202,6 +231,7 @@ MODULE_DEVICE_TABLE(of, msm_sharedmem_of_match); static struct platform_driver msm_sharedmem_driver = { .probe = msm_sharedmem_probe, .remove = msm_sharedmem_remove, + .shutdown = msm_sharedmem_shutdown, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, diff --git a/drivers/usb/gadget/function/f_cdev.c b/drivers/usb/gadget/function/f_cdev.c index 34ec15ab9010..233221fed424 100644 --- a/drivers/usb/gadget/function/f_cdev.c +++ b/drivers/usb/gadget/function/f_cdev.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2013-2017, The Linux Foundation. All rights reserved. + * Copyright (c) 2011, 2013-2018, The Linux Foundation. All rights reserved. * Linux Foundation chooses to take subject only to the GPLv2 license terms, * and distributes only under these terms. * @@ -1251,6 +1251,7 @@ ssize_t f_cdev_write(struct file *file, ret = -EFAULT; } else { req->length = xfer_size; + req->zero = 1; ret = usb_ep_queue(in, req, GFP_KERNEL); if (ret) { pr_err("EP QUEUE failed:%d\n", ret); diff --git a/include/linux/habmm.h b/include/linux/habmm.h index 842cd27fd372..cd4e2506f9ee 100644 --- a/include/linux/habmm.h +++ b/include/linux/habmm.h @@ -14,7 +14,7 @@ #ifndef HABMM_H #define HABMM_H -#include <uapi/linux/habmmid.h> +#include "linux/habmmid.h" #define HAB_API_VER_DEF(_MAJOR_, _MINOR_) \ ((_MAJOR_&0xFF)<<16 | (_MINOR_&0xFFF)) diff --git a/include/linux/iio/imu/mpu.h b/include/linux/iio/imu/mpu.h new file mode 100644 index 000000000000..4dbb86cad6d1 --- /dev/null +++ b/include/linux/iio/imu/mpu.h @@ -0,0 +1,124 @@ +/* +* Copyright (C) 2012-2017 InvenSense, Inc. +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ + +#ifndef __MPU_H_ +#define __MPU_H_ + +#ifdef __KERNEL__ +#include <linux/types.h> +#include <linux/ioctl.h> +#endif + +enum secondary_slave_type { + SECONDARY_SLAVE_TYPE_NONE, + SECONDARY_SLAVE_TYPE_ACCEL, + SECONDARY_SLAVE_TYPE_COMPASS, + SECONDARY_SLAVE_TYPE_PRESSURE, + SECONDARY_SLAVE_TYPE_ALS, + + SECONDARY_SLAVE_TYPE_TYPES +}; + +enum ext_slave_id { + ID_INVALID = 0, + GYRO_ID_MPU3050, + GYRO_ID_MPU6050A2, + GYRO_ID_MPU6050B1, + GYRO_ID_MPU6050B1_NO_ACCEL, + GYRO_ID_ITG3500, + + ACCEL_ID_LIS331, + ACCEL_ID_LSM303DLX, + ACCEL_ID_LIS3DH, + ACCEL_ID_KXSD9, + ACCEL_ID_KXTF9, + ACCEL_ID_BMA150, + ACCEL_ID_BMA222, + ACCEL_ID_BMA250, + ACCEL_ID_ADXL34X, + ACCEL_ID_MMA8450, + ACCEL_ID_MMA845X, + ACCEL_ID_MPU6050, + + COMPASS_ID_AK8963, + COMPASS_ID_AK8975, + COMPASS_ID_AK8972, + COMPASS_ID_AMI30X, + COMPASS_ID_AMI306, + COMPASS_ID_YAS529, + COMPASS_ID_YAS530, + COMPASS_ID_HMC5883, + COMPASS_ID_LSM303DLH, + COMPASS_ID_LSM303DLM, + COMPASS_ID_MMC314X, + COMPASS_ID_HSCDTD002B, + COMPASS_ID_HSCDTD004A, + COMPASS_ID_MLX90399, + COMPASS_ID_AK09911, + COMPASS_ID_AK09912, + COMPASS_ID_AK09916, + + PRESSURE_ID_BMP085, + PRESSURE_ID_BMP280, + + ALS_ID_APDS_9900, + ALS_ID_APDS_9930, + ALS_ID_TSL_2772, +}; + +#define INV_PROD_KEY(ver, rev) (ver * 100 + rev) +/** + * struct mpu_platform_data - Platform data for the mpu driver + * @int_config: Bits [7:3] of the int config register. + * @level_shifter: 0: VLogic, 1: VDD + * @orientation: Orientation matrix of the gyroscope + * @sec_slave_type: secondary slave device type, can be compass, accel, etc + * @sec_slave_id: id of the secondary slave device + * @secondary_i2c_address: secondary device's i2c address + * @secondary_orientation: secondary device's orientation matrix + * @aux_slave_type: auxiliary slave. Another slave device type + * @aux_slave_id: auxiliary slave ID. + * @aux_i2c_addr: auxiliary device I2C address. + * @read_only_slave_type: read only slave type. + * @read_only_slave_id: read only slave device ID. + * @read_only_i2c_addr: read only slave device address. + * + * Contains platform specific information on how to configure the MPU3050 to + * work on this platform. The orientation matricies are 3x3 rotation matricies + * that are applied to the data to rotate from the mounting orientation to the + * platform orientation. The values must be one of 0, 1, or -1 and each row and + * column should have exactly 1 non-zero value. + */ +struct mpu_platform_data { + __u8 int_config; + __u8 level_shifter; + __s8 orientation[9]; + enum secondary_slave_type sec_slave_type; + enum ext_slave_id sec_slave_id; + __u16 secondary_i2c_addr; + __s8 secondary_orientation[9]; + enum secondary_slave_type aux_slave_type; + enum ext_slave_id aux_slave_id; + __u16 aux_i2c_addr; + enum secondary_slave_type read_only_slave_type; + enum ext_slave_id read_only_slave_id; + __u16 read_only_i2c_addr; +#ifdef CONFIG_OF + int (*power_on)(struct mpu_platform_data *); + int (*power_off)(struct mpu_platform_data *); + struct regulator *vdd_ana; + struct regulator *vdd_i2c; +#endif +}; + +#endif /* __MPU_H_ */ diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 64d0797cc3a7..6828063842df 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -260,6 +260,7 @@ enum power_supply_property { POWER_SUPPLY_PROP_PD_VOLTAGE_MAX, POWER_SUPPLY_PROP_PD_VOLTAGE_MIN, POWER_SUPPLY_PROP_SDP_CURRENT_MAX, + POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE, /* Local extensions of type int64_t */ POWER_SUPPLY_PROP_CHARGE_COUNTER_EXT, /* Properties of type `const char *' */ diff --git a/include/sound/q6adm-v2.h b/include/sound/q6adm-v2.h index 4545f2cd3826..f04daf310182 100644 --- a/include/sound/q6adm-v2.h +++ b/include/sound/q6adm-v2.h @@ -128,7 +128,7 @@ int adm_pack_and_set_one_pp_param(int port_id, int copp_idx, int adm_open(int port, int path, int rate, int mode, int topology, int perf_mode, uint16_t bits_per_sample, - int app_type, int acdbdev_id); + int app_type, int acdbdev_id, u32 copp_token); int adm_map_rtac_block(struct rtac_cal_block_data *cal_block); diff --git a/include/uapi/media/ais/msm_ais_isp.h b/include/uapi/media/ais/msm_ais_isp.h index 2b4f0bfeb8c2..649173dd4404 100644 --- a/include/uapi/media/ais/msm_ais_isp.h +++ b/include/uapi/media/ais/msm_ais_isp.h @@ -936,6 +936,13 @@ struct msm_vfe_axi_output_plane_cfg { uint32_t frame_increment; }; +struct msm_vfe_axi_framedrop_update { + enum msm_vfe_axi_stream_src stream_src; + + uint8_t framedrop_period; + uint32_t framedrop_pattern; +}; + struct msm_vfe_axi_output_path_cfg { uint8_t enable; @@ -1003,6 +1010,7 @@ enum msm_isp_ioctl_cmd_code { MSM_ISP_SET_CLK_STATUS, MSM_ISP_CMD_EXT, + MSM_ISP_FRAMEDROP_UPDATE, }; @@ -1130,6 +1138,10 @@ enum msm_isp_ioctl_cmd_code { _IOWR('V', MSM_ISP_AXI_OUTPUT_CFG, \ struct msm_vfe_axi_output_cfg) +#define VIDIOC_MSM_ISP_FRAMEDROP_UPDATE \ + _IOWR('V', MSM_ISP_FRAMEDROP_UPDATE, \ + struct msm_vfe_axi_output_cfg) + #define VIDIOC_MSM_ISP_CAMIF_CFG \ _IOWR('V', MSM_ISP_CAMIF_CFG, \ struct msm_vfe_camif_cfg) diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c index 1535c46808f1..29771c69383b 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c @@ -30,6 +30,7 @@ #include <linux/genhd.h> #include <linux/ktime.h> #include <trace/events/power.h> +#include <soc/qcom/boot_stats.h> #include "power.h" @@ -469,6 +470,7 @@ static int resume_target_kernel(bool platform_mode) touch_softlockup_watchdog(); syscore_resume(); + place_marker("PM: Image Restoration failed!"); Enable_irqs: local_irq_enable(); @@ -705,6 +707,7 @@ int hibernate(void) pm_restore_gfp_mask(); } else { pr_debug("PM: Image restored successfully.\n"); + place_marker("PM: Image restored!"); } Free_bitmaps: diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 392d4e2c0a24..e9a2ff863d9b 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -4156,6 +4156,7 @@ static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u, /* Added to avoid minimal code churn */ struct tpacket_req *req = &req_u->req; + lock_sock(sk); /* Opening a Tx-ring is NOT supported in TPACKET_V3 */ if (!closing && tx_ring && (po->tp_version > TPACKET_V2)) { WARN(1, "Tx-ring is not supported.\n"); @@ -4291,6 +4292,7 @@ static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u, if (pg_vec) free_pg_vec(pg_vec, order, req->tp_block_nr); out: + release_sock(sk); return err; } diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index f08ed375bb91..4490dec28f50 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -13807,7 +13807,8 @@ void cfg80211_ft_event(struct net_device *netdev, if (!ft_event->target_ap) return; - msg = nlmsg_new(100 + ft_event->ric_ies_len, GFP_KERNEL); + msg = nlmsg_new(100 + ft_event->ies_len + ft_event->ric_ies_len, + GFP_KERNEL); if (!msg) return; diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 514380104944..180261da33c9 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -976,9 +976,9 @@ static long snd_rawmidi_kernel_read1(struct snd_rawmidi_substream *substream, struct snd_rawmidi_runtime *runtime = substream->runtime; unsigned long appl_ptr; - spin_lock_irqsave(&runtime->lock, flags); if (userbuf) mutex_lock(&runtime->realloc_mutex); + spin_lock_irqsave(&runtime->lock, flags); while (count > 0 && runtime->avail) { count1 = runtime->buffer_size - runtime->appl_ptr; if (count1 > count) diff --git a/sound/soc/codecs/sdm660_cdc/msm-digital-cdc.c b/sound/soc/codecs/sdm660_cdc/msm-digital-cdc.c index 43f00dcff7af..f4546f4fdcc2 100644 --- a/sound/soc/codecs/sdm660_cdc/msm-digital-cdc.c +++ b/sound/soc/codecs/sdm660_cdc/msm-digital-cdc.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -86,6 +86,8 @@ static int msm_digcdc_clock_control(bool flag) if (flag) { mutex_lock(&pdata->cdc_int_mclk0_mutex); if (atomic_read(&pdata->int_mclk0_enabled) == false) { + if (msm_dig_cdc->regmap->cache_only == true) + return ret; if (pdata->native_clk_set) pdata->digital_cdc_core_clk.clk_freq_in_hz = NATIVE_MCLK_RATE; @@ -103,8 +105,7 @@ static int msm_digcdc_clock_control(bool flag) * Avoid access to lpass register * as clock enable failed during SSR. */ - if (ret == -ENODEV) - msm_dig_cdc->regmap->cache_only = true; + msm_dig_cdc->regmap->cache_only = true; return ret; } pr_debug("enabled digital codec core clk\n"); diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-loopback-v2.c b/sound/soc/msm/qdsp6v2/msm-pcm-loopback-v2.c index 9af5de2952d4..9420534d3c5f 100644 --- a/sound/soc/msm/qdsp6v2/msm-pcm-loopback-v2.c +++ b/sound/soc/msm/qdsp6v2/msm-pcm-loopback-v2.c @@ -570,16 +570,19 @@ static int msm_pcm_playback_app_type_cfg_ctl_put(struct snd_kcontrol *kcontrol, u64 fe_id = kcontrol->private_value; int session_type = SESSION_TYPE_RX; int be_id = ucontrol->value.integer.value[3]; - struct msm_pcm_stream_app_type_cfg cfg_data = {0, 0, 48000}; + struct msm_pcm_stream_app_type_cfg cfg_data = {0, 0, 48000, 0}; int ret = 0; cfg_data.app_type = ucontrol->value.integer.value[0]; cfg_data.acdb_dev_id = ucontrol->value.integer.value[1]; if (ucontrol->value.integer.value[2] != 0) cfg_data.sample_rate = ucontrol->value.integer.value[2]; - pr_debug("%s: fe_id- %llu session_type- %d be_id- %d app_type- %d acdb_dev_id- %d sample_rate- %d\n", + if (ucontrol->value.integer.value[4] != 0) + cfg_data.copp_token = ucontrol->value.integer.value[4]; + pr_debug("%s: fe_id- %llu session_type- %d be_id- %d app_type- %d acdb_dev_id- %d sample_rate- %d copp_token %d\n", __func__, fe_id, session_type, be_id, - cfg_data.app_type, cfg_data.acdb_dev_id, cfg_data.sample_rate); + cfg_data.app_type, cfg_data.acdb_dev_id, cfg_data.sample_rate, + cfg_data.copp_token); ret = msm_pcm_routing_reg_stream_app_type_cfg(fe_id, session_type, be_id, &cfg_data); if (ret < 0) @@ -610,9 +613,12 @@ static int msm_pcm_playback_app_type_cfg_ctl_get(struct snd_kcontrol *kcontrol, ucontrol->value.integer.value[1] = cfg_data.acdb_dev_id; ucontrol->value.integer.value[2] = cfg_data.sample_rate; ucontrol->value.integer.value[3] = be_id; - pr_debug("%s: fedai_id %llu, session_type %d, be_id %d, app_type %d, acdb_dev_id %d, sample_rate %d\n", + ucontrol->value.integer.value[4] = cfg_data.copp_token; + pr_debug("%s: fe_id- %llu session_type- %d be_id- %d app_type- %d acdb_dev_id- %d sample_rate- %d copp_token %d\n", __func__, fe_id, session_type, be_id, - cfg_data.app_type, cfg_data.acdb_dev_id, cfg_data.sample_rate); + cfg_data.app_type, cfg_data.acdb_dev_id, cfg_data.sample_rate, + cfg_data.copp_token); + done: return ret; } @@ -623,16 +629,19 @@ static int msm_pcm_capture_app_type_cfg_ctl_put(struct snd_kcontrol *kcontrol, u64 fe_id = kcontrol->private_value; int session_type = SESSION_TYPE_TX; int be_id = ucontrol->value.integer.value[3]; - struct msm_pcm_stream_app_type_cfg cfg_data = {0, 0, 48000}; + struct msm_pcm_stream_app_type_cfg cfg_data = {0, 0, 48000, 0}; int ret = 0; cfg_data.app_type = ucontrol->value.integer.value[0]; cfg_data.acdb_dev_id = ucontrol->value.integer.value[1]; if (ucontrol->value.integer.value[2] != 0) cfg_data.sample_rate = ucontrol->value.integer.value[2]; - pr_debug("%s: fe_id- %llu session_type- %d be_id- %d app_type- %d acdb_dev_id- %d sample_rate- %d\n", + if (ucontrol->value.integer.value[4] != 0) + cfg_data.copp_token = ucontrol->value.integer.value[4]; + pr_debug("%s: fe_id- %llu session_type- %d be_id- %d app_type- %d acdb_dev_id- %d sample_rate- %d copp_token %d\n", __func__, fe_id, session_type, be_id, - cfg_data.app_type, cfg_data.acdb_dev_id, cfg_data.sample_rate); + cfg_data.app_type, cfg_data.acdb_dev_id, cfg_data.sample_rate, + cfg_data.copp_token); ret = msm_pcm_routing_reg_stream_app_type_cfg(fe_id, session_type, be_id, &cfg_data); if (ret < 0) @@ -663,9 +672,11 @@ static int msm_pcm_capture_app_type_cfg_ctl_get(struct snd_kcontrol *kcontrol, ucontrol->value.integer.value[1] = cfg_data.acdb_dev_id; ucontrol->value.integer.value[2] = cfg_data.sample_rate; ucontrol->value.integer.value[3] = be_id; - pr_debug("%s: fedai_id %llu, session_type %d, be_id %d, app_type %d, acdb_dev_id %d, sample_rate %d\n", + ucontrol->value.integer.value[4] = cfg_data.copp_token; + pr_debug("%s: fe_id- %llu session_type- %d be_id- %d app_type- %d acdb_dev_id- %d sample_rate- %d copp_token %d\n", __func__, fe_id, session_type, be_id, - cfg_data.app_type, cfg_data.acdb_dev_id, cfg_data.sample_rate); + cfg_data.app_type, cfg_data.acdb_dev_id, cfg_data.sample_rate, + cfg_data.copp_token); done: return ret; } diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c b/sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c index 9ab17d87b268..1e69ddcc3464 100644 --- a/sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c +++ b/sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c @@ -660,6 +660,7 @@ static int msm_pcm_open(struct snd_pcm_substream *substream) if (!prtd->audio_client) { pr_info("%s: Could not allocate memory\n", __func__); kfree(prtd); + prtd = NULL; return -ENOMEM; } @@ -1135,6 +1136,12 @@ static int msm_pcm_adsp_stream_cmd_put(struct snd_kcontrol *kcontrol, } prtd = substream->runtime->private_data; + if (prtd == NULL) { + pr_err("%s prtd is null.\n", __func__); + ret = -EINVAL; + goto done; + } + if (prtd->audio_client == NULL) { pr_err("%s prtd is null.\n", __func__); ret = -EINVAL; @@ -1601,16 +1608,19 @@ static int msm_pcm_playback_app_type_cfg_ctl_put(struct snd_kcontrol *kcontrol, u64 fe_id = kcontrol->private_value; int session_type = SESSION_TYPE_RX; int be_id = ucontrol->value.integer.value[3]; - struct msm_pcm_stream_app_type_cfg cfg_data = {0, 0, 48000}; + struct msm_pcm_stream_app_type_cfg cfg_data = {0, 0, 48000, 0}; int ret = 0; cfg_data.app_type = ucontrol->value.integer.value[0]; cfg_data.acdb_dev_id = ucontrol->value.integer.value[1]; if (ucontrol->value.integer.value[2] != 0) cfg_data.sample_rate = ucontrol->value.integer.value[2]; - pr_debug("%s: fe_id- %llu session_type- %d be_id- %d app_type- %d acdb_dev_id- %d sample_rate- %d\n", + if (ucontrol->value.integer.value[4] != 0) + cfg_data.copp_token = ucontrol->value.integer.value[4]; + pr_debug("%s: fe_id- %llu session_type- %d be_id- %d app_type- %d acdb_dev_id- %d sample_rate- %d copp_token %d\n", __func__, fe_id, session_type, be_id, - cfg_data.app_type, cfg_data.acdb_dev_id, cfg_data.sample_rate); + cfg_data.app_type, cfg_data.acdb_dev_id, cfg_data.sample_rate, + cfg_data.copp_token); ret = msm_pcm_routing_reg_stream_app_type_cfg(fe_id, session_type, be_id, &cfg_data); if (ret < 0) @@ -1641,9 +1651,11 @@ static int msm_pcm_playback_app_type_cfg_ctl_get(struct snd_kcontrol *kcontrol, ucontrol->value.integer.value[1] = cfg_data.acdb_dev_id; ucontrol->value.integer.value[2] = cfg_data.sample_rate; ucontrol->value.integer.value[3] = be_id; - pr_debug("%s: fedai_id %llu, session_type %d, be_id %d, app_type %d, acdb_dev_id %d, sample_rate %d\n", + ucontrol->value.integer.value[4] = cfg_data.copp_token; + pr_debug("%s: fe_id- %llu session_type- %d be_id- %d app_type- %d acdb_dev_id- %d sample_rate- %d copp_token %d\n", __func__, fe_id, session_type, be_id, - cfg_data.app_type, cfg_data.acdb_dev_id, cfg_data.sample_rate); + cfg_data.app_type, cfg_data.acdb_dev_id, cfg_data.sample_rate, + cfg_data.copp_token); done: return ret; } @@ -2027,16 +2039,19 @@ static int msm_pcm_capture_app_type_cfg_ctl_put(struct snd_kcontrol *kcontrol, u64 fe_id = kcontrol->private_value; int session_type = SESSION_TYPE_TX; int be_id = ucontrol->value.integer.value[3]; - struct msm_pcm_stream_app_type_cfg cfg_data = {0, 0, 48000}; + struct msm_pcm_stream_app_type_cfg cfg_data = {0, 0, 48000, 0}; int ret = 0; cfg_data.app_type = ucontrol->value.integer.value[0]; cfg_data.acdb_dev_id = ucontrol->value.integer.value[1]; if (ucontrol->value.integer.value[2] != 0) cfg_data.sample_rate = ucontrol->value.integer.value[2]; - pr_debug("%s: fe_id- %llu session_type- %d be_id- %d app_type- %d acdb_dev_id- %d sample_rate- %d\n", + if (ucontrol->value.integer.value[4] != 0) + cfg_data.copp_token = ucontrol->value.integer.value[4]; + pr_debug("%s: fe_id- %llu session_type- %d be_id- %d app_type- %d acdb_dev_id- %d sample_rate- %d copp_token %d\n", __func__, fe_id, session_type, be_id, - cfg_data.app_type, cfg_data.acdb_dev_id, cfg_data.sample_rate); + cfg_data.app_type, cfg_data.acdb_dev_id, cfg_data.sample_rate, + cfg_data.copp_token); ret = msm_pcm_routing_reg_stream_app_type_cfg(fe_id, session_type, be_id, &cfg_data); if (ret < 0) @@ -2067,9 +2082,11 @@ static int msm_pcm_capture_app_type_cfg_ctl_get(struct snd_kcontrol *kcontrol, ucontrol->value.integer.value[1] = cfg_data.acdb_dev_id; ucontrol->value.integer.value[2] = cfg_data.sample_rate; ucontrol->value.integer.value[3] = be_id; - pr_debug("%s: fedai_id %llu, session_type %d, be_id %d, app_type %d, acdb_dev_id %d, sample_rate %d\n", + ucontrol->value.integer.value[4] = cfg_data.copp_token; + pr_debug("%s: fe_id- %llu session_type- %d be_id- %d app_type- %d acdb_dev_id- %d sample_rate- %d copp_token %d\n", __func__, fe_id, session_type, be_id, - cfg_data.app_type, cfg_data.acdb_dev_id, cfg_data.sample_rate); + cfg_data.app_type, cfg_data.acdb_dev_id, cfg_data.sample_rate, + cfg_data.copp_token); done: return ret; } diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.c b/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.c index 4e4970b7be33..532b73e7a127 100644 --- a/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.c +++ b/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.c @@ -116,6 +116,7 @@ enum { #define QUAT_MI2S_TX_TEXT "QUAT_MI2S_TX" #define ADM_LSM_TX_TEXT "ADM_LSM_TX" #define INT3_MI2S_TX_TEXT "INT3_MI2S_TX" +#define QUAT_TDM_TX_0_TEXT "QUAT_TDM_TX_0" #define LSM_FUNCTION_TEXT "LSM Function" static const char * const lsm_port_text[] = { @@ -123,7 +124,7 @@ static const char * const lsm_port_text[] = { SLIMBUS_0_TX_TEXT, SLIMBUS_1_TX_TEXT, SLIMBUS_2_TX_TEXT, SLIMBUS_3_TX_TEXT, SLIMBUS_4_TX_TEXT, SLIMBUS_5_TX_TEXT, TERT_MI2S_TX_TEXT, QUAT_MI2S_TX_TEXT, ADM_LSM_TX_TEXT, - INT3_MI2S_TX_TEXT + INT3_MI2S_TX_TEXT, QUAT_TDM_TX_0_TEXT }; struct msm_pcm_route_bdai_pp_params { @@ -834,10 +835,10 @@ int msm_pcm_routing_reg_stream_app_type_cfg( goto done; } - pr_debug("%s: fedai_id %d, session_type %d, be_id %d, app_type %d, acdb_dev_id %d, sample_rate %d\n", + pr_debug("%s: fedai_id %d, session_type %d, be_id %d, app_type %d, acdb_dev_id %d, sample_rate %d copp_token %d\n", __func__, fedai_id, session_type, be_id, cfg_data->app_type, cfg_data->acdb_dev_id, - cfg_data->sample_rate); + cfg_data->sample_rate, cfg_data->copp_token); if (!is_mm_lsm_fe_id(fedai_id)) { pr_err("%s: Invalid machine driver ID %d\n", @@ -920,10 +921,10 @@ int msm_pcm_routing_get_stream_app_type_cfg( *bedai_id = be_id; *cfg_data = fe_dai_app_type_cfg[fedai_id][session_type][be_id]; - pr_debug("%s: fedai_id %d, session_type %d, be_id %d, app_type %d, acdb_dev_id %d, sample_rate %d\n", + pr_debug("%s: fedai_id %d, session_type %d, be_id %d, app_type %d, acdb_dev_id %d, sample_rate %d copp_token %d\n", __func__, fedai_id, session_type, *bedai_id, cfg_data->app_type, cfg_data->acdb_dev_id, - cfg_data->sample_rate); + cfg_data->sample_rate, cfg_data->copp_token); done: return ret; } @@ -1141,6 +1142,7 @@ int msm_pcm_routing_reg_phy_compr_stream(int fe_id, int perf_mode, u32 channels, sample_rate; u16 bit_width = 16; bool is_lsm; + u32 copp_token = 0; pr_debug("%s:fe_id[%d] perf_mode[%d] id[%d] stream_type[%d] passt[%d]", __func__, fe_id, perf_mode, dspst_id, @@ -1224,6 +1226,8 @@ int msm_pcm_routing_reg_phy_compr_stream(int fe_id, int perf_mode, fe_dai_app_type_cfg[fe_id][session_type][i].sample_rate; bit_width = app_type_cfg[app_type_idx].bit_width; + copp_token = + fe_dai_app_type_cfg[fe_id][session_type][i].copp_token; } else { sample_rate = msm_bedais[i].sample_rate; } @@ -1243,7 +1247,7 @@ int msm_pcm_routing_reg_phy_compr_stream(int fe_id, int perf_mode, adm_open(msm_bedais[i].port_id, path_type, sample_rate, channels, topology, perf_mode, bit_width, - app_type, acdb_dev_id); + app_type, acdb_dev_id, copp_token); if ((copp_idx < 0) || (copp_idx >= MAX_COPPS_PER_PORT)) { pr_err("%s:adm open failed coppid:%d\n", @@ -1476,6 +1480,7 @@ int msm_pcm_routing_reg_phy_stream(int fedai_id, int perf_mode, uint16_t bits_per_sample = 16; uint32_t passthr_mode = LEGACY_PCM; int ret = 0; + u32 copp_token = 0; if (fedai_id > MSM_FRONTEND_DAI_MM_MAX_ID) { /* bad ID assigned in machine driver */ @@ -1531,6 +1536,9 @@ int msm_pcm_routing_reg_phy_stream(int fedai_id, int perf_mode, .sample_rate; bits_per_sample = app_type_cfg[app_type_idx].bit_width; + copp_token = + fe_dai_app_type_cfg[fedai_id][session_type][i] + .copp_token; } else sample_rate = msm_bedais[i].sample_rate; @@ -1543,7 +1551,7 @@ int msm_pcm_routing_reg_phy_stream(int fedai_id, int perf_mode, copp_idx = adm_open(msm_bedais[i].port_id, path_type, sample_rate, channels, topology, perf_mode, bits_per_sample, - app_type, acdb_dev_id); + app_type, acdb_dev_id, copp_token); if ((copp_idx < 0) || (copp_idx >= MAX_COPPS_PER_PORT)) { pr_err("%s: adm open failed copp_idx:%d\n", @@ -1716,6 +1724,7 @@ static void msm_pcm_routing_process_audio(u16 reg, u16 val, int set) struct msm_pcm_routing_fdai_data *fdai; uint32_t passthr_mode; bool is_lsm; + u32 copp_token = 0; pr_debug("%s: reg %x val %x set %x\n", __func__, reg, val, set); @@ -1803,6 +1812,9 @@ static void msm_pcm_routing_process_audio(u16 reg, u16 val, int set) .sample_rate; bits_per_sample = app_type_cfg[app_type_idx].bit_width; + copp_token = + fe_dai_app_type_cfg[val][session_type][reg] + .copp_token; } else sample_rate = msm_bedais[reg].sample_rate; @@ -1814,7 +1826,7 @@ static void msm_pcm_routing_process_audio(u16 reg, u16 val, int set) copp_idx = adm_open(msm_bedais[reg].port_id, path_type, sample_rate, channels, topology, fdai->perf_mode, bits_per_sample, - app_type, acdb_dev_id); + app_type, acdb_dev_id, copp_token); if ((copp_idx < 0) || (copp_idx >= MAX_COPPS_PER_PORT)) { pr_err("%s: adm open failed\n", __func__); @@ -2547,6 +2559,9 @@ static int msm_routing_lsm_port_put(struct snd_kcontrol *kcontrol, case 10: lsm_port = AFE_PORT_ID_INT3_MI2S_TX; break; + case 11: + lsm_port = AFE_PORT_ID_QUATERNARY_TDM_TX; + break; default: pr_err("Default lsm port"); break; @@ -2564,7 +2579,7 @@ static int msm_routing_lsm_func_get(struct snd_kcontrol *kcontrol, u16 port_id; enum afe_mad_type mad_type; - pr_debug("%s: enter\n", __func__); + pr_debug("%s: id.name=%s\n", __func__, kcontrol->id.name); for (i = 0; i < ARRAY_SIZE(lsm_port_text); i++) if (!strnstr(kcontrol->id.name, lsm_port_text[i], strlen(lsm_port_text[i]))) @@ -2590,6 +2605,10 @@ static int msm_routing_lsm_func_get(struct snd_kcontrol *kcontrol, strlen(lsm_port_text[10]))) port_id = AFE_PORT_ID_INT3_MI2S_TX; + if (strnstr(kcontrol->id.name, lsm_port_text[11], + strlen(lsm_port_text[11]))) + port_id = AFE_PORT_ID_QUATERNARY_TDM_TX; + mad_type = afe_port_get_mad_type(port_id); pr_debug("%s: port_id 0x%x, mad_type %d\n", __func__, port_id, mad_type); @@ -2623,7 +2642,7 @@ static int msm_routing_lsm_func_put(struct snd_kcontrol *kcontrol, u16 port_id; enum afe_mad_type mad_type; - pr_debug("%s: enter\n", __func__); + pr_debug("%s: id.name=%s\n", __func__, kcontrol->id.name); for (i = 0; i < ARRAY_SIZE(lsm_port_text); i++) if (strnstr(kcontrol->id.name, lsm_port_text[i], strlen(lsm_port_text[i]))) @@ -2669,6 +2688,10 @@ static int msm_routing_lsm_func_put(struct snd_kcontrol *kcontrol, strlen(lsm_port_text[10]))) port_id = AFE_PORT_ID_INT3_MI2S_TX; + if (strnstr(kcontrol->id.name, lsm_port_text[11], + strlen(lsm_port_text[11]))) + port_id = AFE_PORT_ID_QUATERNARY_TDM_TX; + pr_debug("%s: port_id 0x%x, mad_type %d\n", __func__, port_id, mad_type); return afe_port_set_mad_type(port_id, mad_type); @@ -11049,6 +11072,9 @@ static const struct snd_kcontrol_new lsm1_mixer_controls[] = { SOC_SINGLE_EXT("INT3_MI2S_TX", MSM_BACKEND_DAI_INT3_MI2S_TX, MSM_FRONTEND_DAI_LSM1, 1, 0, msm_routing_get_listen_mixer, msm_routing_put_listen_mixer), + SOC_SINGLE_EXT("QUAT_TDM_TX_0", MSM_BACKEND_DAI_QUAT_TDM_TX_0, + MSM_FRONTEND_DAI_LSM1, 1, 0, msm_routing_get_listen_mixer, + msm_routing_put_listen_mixer), }; static const struct snd_kcontrol_new lsm2_mixer_controls[] = { @@ -11076,6 +11102,9 @@ static const struct snd_kcontrol_new lsm2_mixer_controls[] = { SOC_SINGLE_EXT("INT3_MI2S_TX", MSM_BACKEND_DAI_INT3_MI2S_TX, MSM_FRONTEND_DAI_LSM2, 1, 0, msm_routing_get_listen_mixer, msm_routing_put_listen_mixer), + SOC_SINGLE_EXT("QUAT_TDM_TX_0", MSM_BACKEND_DAI_QUAT_TDM_TX_0, + MSM_FRONTEND_DAI_LSM2, 1, 0, msm_routing_get_listen_mixer, + msm_routing_put_listen_mixer), }; static const struct snd_kcontrol_new lsm3_mixer_controls[] = { @@ -11103,6 +11132,9 @@ static const struct snd_kcontrol_new lsm3_mixer_controls[] = { SOC_SINGLE_EXT("INT3_MI2S_TX", MSM_BACKEND_DAI_INT3_MI2S_TX, MSM_FRONTEND_DAI_LSM3, 1, 0, msm_routing_get_listen_mixer, msm_routing_put_listen_mixer), + SOC_SINGLE_EXT("QUAT_TDM_TX_0", MSM_BACKEND_DAI_QUAT_TDM_TX_0, + MSM_FRONTEND_DAI_LSM3, 1, 0, msm_routing_get_listen_mixer, + msm_routing_put_listen_mixer), }; static const struct snd_kcontrol_new lsm4_mixer_controls[] = { @@ -11130,6 +11162,9 @@ static const struct snd_kcontrol_new lsm4_mixer_controls[] = { SOC_SINGLE_EXT("INT3_MI2S_TX", MSM_BACKEND_DAI_INT3_MI2S_TX, MSM_FRONTEND_DAI_LSM4, 1, 0, msm_routing_get_listen_mixer, msm_routing_put_listen_mixer), + SOC_SINGLE_EXT("QUAT_TDM_TX_0", MSM_BACKEND_DAI_QUAT_TDM_TX_0, + MSM_FRONTEND_DAI_LSM4, 1, 0, msm_routing_get_listen_mixer, + msm_routing_put_listen_mixer), }; static const struct snd_kcontrol_new lsm5_mixer_controls[] = { @@ -11157,6 +11192,9 @@ static const struct snd_kcontrol_new lsm5_mixer_controls[] = { SOC_SINGLE_EXT("INT3_MI2S_TX", MSM_BACKEND_DAI_INT3_MI2S_TX, MSM_FRONTEND_DAI_LSM5, 1, 0, msm_routing_get_listen_mixer, msm_routing_put_listen_mixer), + SOC_SINGLE_EXT("QUAT_TDM_TX_0", MSM_BACKEND_DAI_QUAT_TDM_TX_0, + MSM_FRONTEND_DAI_LSM5, 1, 0, msm_routing_get_listen_mixer, + msm_routing_put_listen_mixer), }; static const struct snd_kcontrol_new lsm6_mixer_controls[] = { @@ -11184,6 +11222,9 @@ static const struct snd_kcontrol_new lsm6_mixer_controls[] = { SOC_SINGLE_EXT("INT3_MI2S_TX", MSM_BACKEND_DAI_INT3_MI2S_TX, MSM_FRONTEND_DAI_LSM6, 1, 0, msm_routing_get_listen_mixer, msm_routing_put_listen_mixer), + SOC_SINGLE_EXT("QUAT_TDM_TX_0", MSM_BACKEND_DAI_QUAT_TDM_TX_0, + MSM_FRONTEND_DAI_LSM6, 1, 0, msm_routing_get_listen_mixer, + msm_routing_put_listen_mixer), }; static const struct snd_kcontrol_new lsm7_mixer_controls[] = { @@ -11211,6 +11252,9 @@ static const struct snd_kcontrol_new lsm7_mixer_controls[] = { SOC_SINGLE_EXT("INT3_MI2S_TX", MSM_BACKEND_DAI_INT3_MI2S_TX, MSM_FRONTEND_DAI_LSM7, 1, 0, msm_routing_get_listen_mixer, msm_routing_put_listen_mixer), + SOC_SINGLE_EXT("QUAT_TDM_TX_0", MSM_BACKEND_DAI_QUAT_TDM_TX_0, + MSM_FRONTEND_DAI_LSM7, 1, 0, msm_routing_get_listen_mixer, + msm_routing_put_listen_mixer), }; static const struct snd_kcontrol_new lsm8_mixer_controls[] = { @@ -11238,6 +11282,9 @@ static const struct snd_kcontrol_new lsm8_mixer_controls[] = { SOC_SINGLE_EXT("INT3_MI2S_TX", MSM_BACKEND_DAI_INT3_MI2S_TX, MSM_FRONTEND_DAI_LSM8, 1, 0, msm_routing_get_listen_mixer, msm_routing_put_listen_mixer), + SOC_SINGLE_EXT("QUAT_TDM_TX_0", MSM_BACKEND_DAI_QUAT_TDM_TX_0, + MSM_FRONTEND_DAI_LSM8, 1, 0, msm_routing_get_listen_mixer, + msm_routing_put_listen_mixer), }; static const struct snd_kcontrol_new slim_fm_switch_mixer_controls = @@ -11354,6 +11401,8 @@ static const struct snd_kcontrol_new lsm_controls[] = { msm_routing_lsm_func_get, msm_routing_lsm_func_put), SOC_ENUM_EXT(INT3_MI2S_TX_TEXT" "LSM_FUNCTION_TEXT, lsm_func_enum, msm_routing_lsm_func_get, msm_routing_lsm_func_put), + SOC_ENUM_EXT(QUAT_TDM_TX_0_TEXT" "LSM_FUNCTION_TEXT, lsm_func_enum, + msm_routing_lsm_func_get, msm_routing_lsm_func_put), /* kcontrol of lsm_port */ SOC_ENUM_EXT("LSM1 Port", lsm_port_enum, msm_routing_lsm_port_get, @@ -11379,6 +11428,9 @@ static const struct snd_kcontrol_new lsm_controls[] = { SOC_ENUM_EXT("LSM8 Port", lsm_port_enum, msm_routing_lsm_port_get, msm_routing_lsm_port_put), + SOC_ENUM_EXT("LSM9 Port", lsm_port_enum, + msm_routing_lsm_port_get, + msm_routing_lsm_port_put), }; static const char * const aanc_slim_0_rx_text[] = { @@ -15200,6 +15252,7 @@ static const struct snd_soc_dapm_route intercon[] = { {"LSM1 Mixer", "TERT_MI2S_TX", "TERT_MI2S_TX"}, {"LSM1 Mixer", "QUAT_MI2S_TX", "QUAT_MI2S_TX"}, {"LSM1 Mixer", "INT3_MI2S_TX", "INT3_MI2S_TX"}, + {"LSM1 Mixer", "QUAT_TDM_TX_0", "QUAT_TDM_TX_0"}, {"LSM1_UL_HL", NULL, "LSM1 Mixer"}, {"LSM2 Mixer", "SLIMBUS_0_TX", "SLIMBUS_0_TX"}, @@ -15210,6 +15263,7 @@ static const struct snd_soc_dapm_route intercon[] = { {"LSM2 Mixer", "TERT_MI2S_TX", "TERT_MI2S_TX"}, {"LSM2 Mixer", "QUAT_MI2S_TX", "QUAT_MI2S_TX"}, {"LSM2 Mixer", "INT3_MI2S_TX", "INT3_MI2S_TX"}, + {"LSM2 Mixer", "QUAT_TDM_TX_0", "QUAT_TDM_TX_0"}, {"LSM2_UL_HL", NULL, "LSM2 Mixer"}, @@ -15221,6 +15275,7 @@ static const struct snd_soc_dapm_route intercon[] = { {"LSM3 Mixer", "TERT_MI2S_TX", "TERT_MI2S_TX"}, {"LSM3 Mixer", "QUAT_MI2S_TX", "QUAT_MI2S_TX"}, {"LSM3 Mixer", "INT3_MI2S_TX", "INT3_MI2S_TX"}, + {"LSM3 Mixer", "QUAT_TDM_TX_0", "QUAT_TDM_TX_0"}, {"LSM3_UL_HL", NULL, "LSM3 Mixer"}, @@ -15232,6 +15287,7 @@ static const struct snd_soc_dapm_route intercon[] = { {"LSM4 Mixer", "TERT_MI2S_TX", "TERT_MI2S_TX"}, {"LSM4 Mixer", "QUAT_MI2S_TX", "QUAT_MI2S_TX"}, {"LSM4 Mixer", "INT3_MI2S_TX", "INT3_MI2S_TX"}, + {"LSM4 Mixer", "QUAT_TDM_TX_0", "QUAT_TDM_TX_0"}, {"LSM4_UL_HL", NULL, "LSM4 Mixer"}, {"LSM5 Mixer", "SLIMBUS_0_TX", "SLIMBUS_0_TX"}, @@ -15242,6 +15298,7 @@ static const struct snd_soc_dapm_route intercon[] = { {"LSM5 Mixer", "TERT_MI2S_TX", "TERT_MI2S_TX"}, {"LSM5 Mixer", "QUAT_MI2S_TX", "QUAT_MI2S_TX"}, {"LSM5 Mixer", "INT3_MI2S_TX", "INT3_MI2S_TX"}, + {"LSM5 Mixer", "QUAT_TDM_TX_0", "QUAT_TDM_TX_0"}, {"LSM5_UL_HL", NULL, "LSM5 Mixer"}, {"LSM6 Mixer", "SLIMBUS_0_TX", "SLIMBUS_0_TX"}, @@ -15250,6 +15307,7 @@ static const struct snd_soc_dapm_route intercon[] = { {"LSM6 Mixer", "SLIMBUS_4_TX", "SLIMBUS_4_TX"}, {"LSM6 Mixer", "SLIMBUS_5_TX", "SLIMBUS_5_TX"}, {"LSM6 Mixer", "QUAT_MI2S_TX", "QUAT_MI2S_TX"}, + {"LSM6 Mixer", "QUAT_TDM_TX_0", "QUAT_TDM_TX_0"}, {"LSM6_UL_HL", NULL, "LSM6 Mixer"}, {"LSM7 Mixer", "SLIMBUS_0_TX", "SLIMBUS_0_TX"}, @@ -15258,6 +15316,7 @@ static const struct snd_soc_dapm_route intercon[] = { {"LSM7 Mixer", "SLIMBUS_4_TX", "SLIMBUS_4_TX"}, {"LSM7 Mixer", "SLIMBUS_5_TX", "SLIMBUS_5_TX"}, {"LSM7 Mixer", "QUAT_MI2S_TX", "QUAT_MI2S_TX"}, + {"LSM7 Mixer", "QUAT_TDM_TX_0", "QUAT_TDM_TX_0"}, {"LSM7_UL_HL", NULL, "LSM7 Mixer"}, {"LSM8 Mixer", "SLIMBUS_0_TX", "SLIMBUS_0_TX"}, @@ -15266,6 +15325,7 @@ static const struct snd_soc_dapm_route intercon[] = { {"LSM8 Mixer", "SLIMBUS_4_TX", "SLIMBUS_4_TX"}, {"LSM8 Mixer", "SLIMBUS_5_TX", "SLIMBUS_5_TX"}, {"LSM8 Mixer", "QUAT_MI2S_TX", "QUAT_MI2S_TX"}, + {"LSM8 Mixer", "QUAT_TDM_TX_0", "QUAT_TDM_TX_0"}, {"LSM8_UL_HL", NULL, "LSM8 Mixer"}, @@ -16127,6 +16187,7 @@ static int msm_pcm_routing_prepare(struct snd_pcm_substream *substream) u32 session_id; struct media_format_info voc_be_media_format; bool is_lsm; + u32 copp_token = 0; pr_debug("%s: substream->pcm->id:%s\n", __func__, substream->pcm->id); @@ -16204,6 +16265,9 @@ static int msm_pcm_routing_prepare(struct snd_pcm_substream *substream) [be_id].sample_rate; bits_per_sample = app_type_cfg[app_type_idx].bit_width; + copp_token = + fe_dai_app_type_cfg[i][session_type] + [be_id].copp_token; } else sample_rate = bedai->sample_rate; /* @@ -16221,7 +16285,7 @@ static int msm_pcm_routing_prepare(struct snd_pcm_substream *substream) copp_idx = adm_open(bedai->port_id, path_type, sample_rate, channels, topology, fdai->perf_mode, bits_per_sample, - app_type, acdb_dev_id); + app_type, acdb_dev_id, copp_token); if ((copp_idx < 0) || (copp_idx >= MAX_COPPS_PER_PORT)) { pr_err("%s: adm open failed\n", __func__); diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.h b/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.h index 63c90dc35c90..531f83d752b0 100644 --- a/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.h +++ b/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.h @@ -465,6 +465,7 @@ struct msm_pcm_stream_app_type_cfg { int app_type; int acdb_dev_id; int sample_rate; + u32 copp_token; }; /* dai_id: front-end ID, diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-voice-v2.c b/sound/soc/msm/qdsp6v2/msm-pcm-voice-v2.c index 76d8f8d9e33c..1b8150e5a30f 100644 --- a/sound/soc/msm/qdsp6v2/msm-pcm-voice-v2.c +++ b/sound/soc/msm/qdsp6v2/msm-pcm-voice-v2.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -28,8 +28,15 @@ #include <linux/of_device.h> #include "msm-pcm-voice-v2.h" +#include "msm-qti-pp-config.h" #include "q6voice.h" +struct msm_dtmf_detected_event_data { + uint32_t event_type; + uint32_t payload_len; + struct vss_istream_evt_rx_dtmf_detected dtmf_payload; +}; + static struct msm_voice voice_info[VOICE_SESSION_INDEX_MAX]; static struct snd_pcm_hardware msm_pcm_hardware = { @@ -52,6 +59,44 @@ static struct snd_pcm_hardware msm_pcm_hardware = { .fifo_size = 0, }; + +static int get_idx_for_session(uint32_t session_id) +{ + int idx = 0; + + switch (session_id) { + case VOICE_SESSION_VSID: + idx = VOICE_SESSION_INDEX; + break; + case VOICE2_SESSION_VSID: + idx = VOICE2_SESSION_INDEX; + break; + case VOLTE_SESSION_VSID: + idx = VOLTE_SESSION_INDEX; + break; + case QCHAT_SESSION_VSID: + idx = QCHAT_SESSION_INDEX; + break; + case VOWLAN_SESSION_VSID: + idx = VOWLAN_SESSION_INDEX; + break; + case VOICEMMODE1_VSID: + idx = VOICEMMODE1_INDEX; + break; + case VOICEMMODE2_VSID: + idx = VOICEMMODE2_INDEX; + break; + case ALL_SESSION_VSID: + idx = VOICE_SESSION_INDEX_MAX - 1; + break; + default: + pr_err("%s: Invalid session_id : %x\n", __func__, session_id); + break; + } + + return idx; +} + static bool is_volte(struct msm_voice *pvolte) { if (pvolte == &voice_info[VOLTE_SESSION_INDEX]) @@ -122,6 +167,38 @@ static uint32_t get_session_id(struct msm_voice *pvoc) return session_id; } +static void dtmf_rx_detected_evt_hdlr(uint8_t *pkt, + char *session, + void *private_data) +{ + int ret = 0; + struct snd_soc_pcm_runtime *soc_prtd = private_data; + struct msm_dtmf_detected_event_data event_data = {ADSP_STREAM_PP_EVENT, + sizeof(struct vss_istream_evt_rx_dtmf_detected), + {0} }; + + if (!pkt) { + pr_err("%s: packet is NULL\n", + __func__); + return; + } + + if (!private_data) { + pr_err("%s: private_data is NULL\n", + __func__); + return; + } + + memcpy(&event_data.dtmf_payload, pkt, + sizeof(struct vss_istream_evt_rx_dtmf_detected)); + + ret = msm_adsp_inform_mixer_ctl(soc_prtd, (uint32_t *)&event_data); + if (ret) { + pr_err("%s: failed to inform mixer ctl. err = %d\n", + __func__, ret); + return; + } +} static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) { @@ -151,6 +228,7 @@ static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) static int msm_pcm_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; struct msm_voice *voice; if (!strncmp("VoLTE", substream->pcm->id, 5)) { @@ -197,7 +275,7 @@ static int msm_pcm_open(struct snd_pcm_substream *substream) runtime->private_data = voice; mutex_unlock(&voice->lock); - + msm_adsp_init_mixer_ctl_pp_event_queue(soc_prtd); return 0; } static int msm_pcm_playback_close(struct snd_pcm_substream *substream) @@ -231,6 +309,7 @@ static int msm_pcm_close(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; struct msm_voice *prtd = runtime->private_data; uint32_t session_id = 0; int ret = 0; @@ -250,6 +329,7 @@ static int msm_pcm_close(struct snd_pcm_substream *substream) voc_end_voice_call(session_id); } mutex_unlock(&prtd->lock); + msm_adsp_clean_mixer_ctl_pp_event_queue(soc_prtd); return ret; } @@ -583,6 +663,41 @@ static int msm_voice_hd_voice_put(struct snd_kcontrol *kcontrol, return ret; } +static int msm_dtmf_detect_rx_vsid_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + uint32_t session_id = ucontrol->value.integer.value[0]; + uint32_t enable = ucontrol->value.integer.value[1]; + + if (enable) + enable = 1; + + pr_debug("%s: sess_id=%d enable=%d\n", __func__, session_id, enable); + + return voc_enable_dtmf_rx_detection(session_id, enable); +} + +static int msm_dtmf_detect_rx_vsid_cb_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct msm_voice *dtmf_voice_info = NULL; + uint32_t session_id = ucontrol->value.integer.value[0]; + uint32_t enable = ucontrol->value.integer.value[1]; + + if (enable) + enable = 1; + + pr_debug("%s: enable dtmf detect cb =%d for session_id=%d\n", + __func__, enable, session_id); + + dtmf_voice_info = &voice_info[get_idx_for_session(session_id)]; + voc_register_dtmf_rx_detection_cb + ((dtmf_rx_det_cb_fn) dtmf_rx_detected_evt_hdlr, + (void *) dtmf_voice_info->capture_substream->private_data); + + return 0; +} + static int msm_voice_topology_disable_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -636,6 +751,7 @@ static int msm_voice_cvd_version_get(struct snd_kcontrol *kcontrol, return 0; } + static struct snd_kcontrol_new msm_voice_controls[] = { SOC_SINGLE_MULTI_EXT("Voice Rx Device Mute", SND_SOC_NOPM, 0, VSID_MAX, 0, 3, NULL, msm_voice_rx_device_mute_put), @@ -654,6 +770,12 @@ static struct snd_kcontrol_new msm_voice_controls[] = { msm_voice_topology_disable_put), SOC_SINGLE_MULTI_EXT("HD Voice Enable", SND_SOC_NOPM, 0, VSID_MAX, 0, 2, NULL, msm_voice_hd_voice_put), + SOC_SINGLE_MULTI_EXT("DTMF_Detect Rx VSID enable", + SND_SOC_NOPM, 0, VSID_MAX, 0, 2, + NULL, msm_dtmf_detect_rx_vsid_put), + SOC_SINGLE_MULTI_EXT("DTMF_Detect Rx Callback VSID enable", + SND_SOC_NOPM, 0, VSID_MAX, 0, 2, + NULL, msm_dtmf_detect_rx_vsid_cb_put), { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, @@ -666,6 +788,118 @@ static struct snd_kcontrol_new msm_voice_controls[] = { }; +static int msm_pcm_adsp_stream_cmd_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} + +static int msm_pcm_add_voice_adsp_stream_cmd_control( + struct snd_soc_pcm_runtime *rtd) +{ + const char *mixer_ctl_name = DSP_STREAM_CMD; + const char *deviceNo = "NN"; + char *mixer_str = NULL; + int ctl_len = 0, ret = 0; + struct snd_kcontrol_new fe_voice_adsp_stream_cmd_config_control[1] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "?", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = msm_adsp_stream_cmd_info, + .put = msm_pcm_adsp_stream_cmd_put, + .private_value = 0, + } + }; + + if (!rtd) { + pr_err("%s rtd is NULL\n", __func__); + return -EINVAL; + } + + ctl_len = strlen(mixer_ctl_name) + 1 + strlen(deviceNo) + 1; + mixer_str = kzalloc(ctl_len, GFP_KERNEL); + if (!mixer_str) + return -ENOMEM; + + snprintf(mixer_str, ctl_len, "%s %d", mixer_ctl_name, rtd->pcm->device); + fe_voice_adsp_stream_cmd_config_control[0].name = mixer_str; + fe_voice_adsp_stream_cmd_config_control[0].private_value = + rtd->dai_link->be_id; + pr_debug("Registering new mixer ctl %s\n", mixer_str); + ret = snd_soc_add_platform_controls(rtd->platform, + fe_voice_adsp_stream_cmd_config_control, + ARRAY_SIZE(fe_voice_adsp_stream_cmd_config_control)); + if (ret < 0) + pr_err("%s: failed add ctl %s. err = %d\n", + __func__, mixer_str, ret); + + kfree(mixer_str); + return ret; +} + +static int msm_pcm_add_voice_adsp_stream_callback_control( + struct snd_soc_pcm_runtime *rtd) +{ + const char *mixer_ctl_name = DSP_STREAM_CALLBACK; + const char *deviceNo = "NN"; + char *mixer_str = NULL; + int ctl_len = 0, ret = 0; + struct snd_kcontrol *kctl; + + struct snd_kcontrol_new fe_voice_adsp_callback_config_control[1] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "?", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = msm_adsp_stream_callback_info, + .get = msm_adsp_stream_callback_get, + .private_value = 0, + } + }; + + if (!rtd) { + pr_err("%s NULL rtd\n", __func__); + return -EINVAL; + } + + pr_debug("%s: added new pcm FE with name %s, id %d, cpu dai %s, device no %d\n", + __func__, rtd->dai_link->name, rtd->dai_link->be_id, + rtd->dai_link->cpu_dai_name, rtd->pcm->device); + ctl_len = strlen(mixer_ctl_name) + 1 + strlen(deviceNo) + 1; + mixer_str = kzalloc(ctl_len, GFP_KERNEL); + if (!mixer_str) + return -ENOMEM; + + snprintf(mixer_str, ctl_len, "%s %d", mixer_ctl_name, rtd->pcm->device); + fe_voice_adsp_callback_config_control[0].name = mixer_str; + fe_voice_adsp_callback_config_control[0].private_value = + rtd->dai_link->be_id; + pr_debug("%s: Registering new mixer ctl %s\n", __func__, mixer_str); + ret = snd_soc_add_platform_controls(rtd->platform, + fe_voice_adsp_callback_config_control, + ARRAY_SIZE(fe_voice_adsp_callback_config_control)); + if (ret < 0) { + pr_err("%s: failed to add ctl %s. err = %d\n", + __func__, mixer_str, ret); + ret = -EINVAL; + goto free_mixer_str; + } + + kctl = snd_soc_card_get_kcontrol(rtd->card, mixer_str); + if (!kctl) { + pr_err("%s: failed to get kctl %s.\n", __func__, mixer_str); + ret = -EINVAL; + goto free_mixer_str; + } + + kctl->private_data = NULL; + +free_mixer_str: + kfree(mixer_str); + return ret; +} + static struct snd_pcm_ops msm_pcm_ops = { .open = msm_pcm_open, .hw_params = msm_pcm_hw_params, @@ -684,6 +918,17 @@ static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) if (!card->dev->coherent_dma_mask) card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + ret = msm_pcm_add_voice_adsp_stream_cmd_control(rtd); + if (ret) + pr_err("%s: Could not add pcm ADSP Stream Cmd Control\n", + __func__); + + ret = msm_pcm_add_voice_adsp_stream_callback_control(rtd); + if (ret) + pr_err("%s: Could not add pcm ADSP Stream Callback Control\n", + __func__); + return ret; } diff --git a/sound/soc/msm/qdsp6v2/q6adm.c b/sound/soc/msm/qdsp6v2/q6adm.c index 24d4199ac30b..c6f3b5ba96ba 100644 --- a/sound/soc/msm/qdsp6v2/q6adm.c +++ b/sound/soc/msm/qdsp6v2/q6adm.c @@ -67,6 +67,7 @@ struct adm_copp { atomic_t adm_delay_stat[AFE_MAX_PORTS][MAX_COPPS_PER_PORT]; uint32_t adm_delay[AFE_MAX_PORTS][MAX_COPPS_PER_PORT]; unsigned long adm_status[AFE_MAX_PORTS][MAX_COPPS_PER_PORT]; + atomic_t token[AFE_MAX_PORTS][MAX_COPPS_PER_PORT]; }; struct source_tracking_data { @@ -102,6 +103,7 @@ struct adm_ctl { int ec_ref_rx_sampling_rate; int native_mode; + u32 copp_token; }; static struct adm_ctl this_adm; @@ -222,14 +224,45 @@ static int adm_get_copp_id(int port_idx, int copp_idx) return atomic_read(&this_adm.copp.id[port_idx][copp_idx]); } +static int adm_get_idx_if_single_copp_exists(int port_idx, + int topology, int mode, + int rate, int bit_width, + u32 copp_token) +{ + int idx; + + pr_debug("%s: copp_token %d\n", __func__, copp_token); + + for (idx = 0; idx < MAX_COPPS_PER_PORT; idx++) + if ((topology == + atomic_read(&this_adm.copp.topology[port_idx][idx])) && + (mode == + atomic_read(&this_adm.copp.mode[port_idx][idx])) && + (rate == + atomic_read(&this_adm.copp.rate[port_idx][idx])) && + (bit_width == + atomic_read(&this_adm.copp.bit_width[port_idx][idx])) && + (copp_token == + atomic_read(&this_adm.copp.token[port_idx][idx]))) + return idx; + return -EINVAL; +} + static int adm_get_idx_if_copp_exists(int port_idx, int topology, int mode, - int rate, int bit_width, int app_type) + int rate, int bit_width, int app_type, + u32 copp_token) { int idx; pr_debug("%s: port_idx-%d, topology-0x%x, mode-%d, rate-%d, bit_width-%d\n", __func__, port_idx, topology, mode, rate, bit_width); + if (copp_token) + return adm_get_idx_if_single_copp_exists(port_idx, + topology, mode, + rate, bit_width, + copp_token); + for (idx = 0; idx < MAX_COPPS_PER_PORT; idx++) if ((topology == atomic_read(&this_adm.copp.topology[port_idx][idx])) && @@ -949,7 +982,7 @@ int adm_get_pp_params(int port_id, int copp_idx, uint32_t client_id, copp_stat = &this_adm.copp.stat[port_idx][copp_idx]; atomic_set(copp_stat, -1); ret = apr_send_pkt(this_adm.apr, (uint32_t *) &adm_get_params); - if (ret) { + if (ret < 0) { pr_err("%s: Get params APR send failed port = 0x%x ret %d\n", __func__, port_id, ret); ret = -EINVAL; @@ -1064,7 +1097,7 @@ int adm_get_pp_topo_module_list_v2(int port_id, int copp_idx, copp_stat = &this_adm.copp.stat[port_idx][copp_idx]; atomic_set(copp_stat, -1); ret = apr_send_pkt(this_adm.apr, (uint32_t *) &adm_get_module_list); - if (ret) { + if (ret < 0) { pr_err("%s: APR send pkt failed for port_id: 0x%x failed ret %d\n", __func__, port_id, ret); ret = -EINVAL; @@ -2804,7 +2837,8 @@ static int adm_open_v5_v6(int tmp_port, int port_idx, int copp_idx, } int adm_open(int port_id, int path, int rate, int channel_mode, int topology, - int perf_mode, uint16_t bit_width, int app_type, int acdb_id) + int perf_mode, uint16_t bit_width, int app_type, int acdb_id, + u32 copp_token) { int ret = 0; int port_idx, flags; @@ -2871,7 +2905,7 @@ int adm_open(int port_id, int path, int rate, int channel_mode, int topology, copp_idx = adm_get_idx_if_copp_exists(port_idx, topology, perf_mode, rate, bit_width, - app_type); + app_type, copp_token); if (copp_idx < 0) { copp_idx = adm_get_next_available_copp(port_idx); @@ -2895,6 +2929,8 @@ int adm_open(int port_id, int path, int rate, int channel_mode, int topology, app_type); atomic_set(&this_adm.copp.acdb_id[port_idx][copp_idx], acdb_id); + atomic_set(&this_adm.copp.token[port_idx][copp_idx], + copp_token); set_bit(ADM_STATUS_CALIBRATION_REQUIRED, (void *)&this_adm.copp.adm_status[port_idx][copp_idx]); if ((path != ADM_PATH_COMPRESSED_RX) && @@ -3432,6 +3468,7 @@ int adm_close(int port_id, int perf_mode, int copp_idx) atomic_set(&this_adm.copp.channels[port_idx][copp_idx], 0); atomic_set(&this_adm.copp.bit_width[port_idx][copp_idx], 0); atomic_set(&this_adm.copp.app_type[port_idx][copp_idx], 0); + atomic_set(&this_adm.copp.token[port_idx][copp_idx], 0); clear_bit(ADM_STATUS_CALIBRATION_REQUIRED, (void *)&this_adm.copp.adm_status[port_idx][copp_idx]); diff --git a/sound/soc/msm/qdsp6v2/q6afe.c b/sound/soc/msm/qdsp6v2/q6afe.c index 38dc3639a682..0524ca21feba 100644 --- a/sound/soc/msm/qdsp6v2/q6afe.c +++ b/sound/soc/msm/qdsp6v2/q6afe.c @@ -2374,7 +2374,8 @@ int afe_port_set_mad_type(u16 port_id, enum afe_mad_type mad_type) int i; if (port_id == AFE_PORT_ID_TERTIARY_MI2S_TX || - port_id == AFE_PORT_ID_INT3_MI2S_TX) { + port_id == AFE_PORT_ID_INT3_MI2S_TX || + port_id == AFE_PORT_ID_QUATERNARY_TDM_TX) { mad_type = MAD_SW_AUDIO; return 0; } @@ -2393,7 +2394,8 @@ enum afe_mad_type afe_port_get_mad_type(u16 port_id) int i; if (port_id == AFE_PORT_ID_TERTIARY_MI2S_TX || - port_id == AFE_PORT_ID_INT3_MI2S_TX) + port_id == AFE_PORT_ID_INT3_MI2S_TX || + port_id == AFE_PORT_ID_QUATERNARY_TDM_TX) return MAD_SW_AUDIO; i = port_id - SLIMBUS_0_RX; diff --git a/sound/soc/msm/qdsp6v2/q6asm.c b/sound/soc/msm/qdsp6v2/q6asm.c index 9d3fa1afeb6d..0ad15e90bfc6 100644 --- a/sound/soc/msm/qdsp6v2/q6asm.c +++ b/sound/soc/msm/qdsp6v2/q6asm.c @@ -1086,7 +1086,7 @@ int q6asm_unmap_rtac_block(uint32_t *mem_map_handle) __func__, result2); result = result2; } else { - mem_map_handle = 0; + *mem_map_handle = 0; } result2 = q6asm_mmap_apr_dereg(); diff --git a/sound/soc/msm/qdsp6v2/q6voice.c b/sound/soc/msm/qdsp6v2/q6voice.c index 17d78704709f..69189140c936 100644 --- a/sound/soc/msm/qdsp6v2/q6voice.c +++ b/sound/soc/msm/qdsp6v2/q6voice.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -6861,8 +6861,8 @@ static int32_t qdsp_cvs_callback(struct apr_client_data *data, void *priv) pr_debug("Recd VSS_ISTREAM_EVT_NOT_READY\n"); } else if (data->opcode == VSS_ISTREAM_EVT_READY) { pr_debug("Recd VSS_ISTREAM_EVT_READY\n"); - } else if (data->opcode == VSS_ICOMMON_RSP_GET_PARAM || - VSS_ICOMMON_RSP_GET_PARAM_V3) { + } else if ((data->opcode == VSS_ICOMMON_RSP_GET_PARAM) || + (data->opcode == VSS_ICOMMON_RSP_GET_PARAM_V3)) { pr_debug("%s: VSS_ICOMMON_RSP_GET_PARAM\n", __func__); ptr = data->payload; if (ptr[0] != 0) { @@ -6871,7 +6871,7 @@ static int32_t qdsp_cvs_callback(struct apr_client_data *data, void *priv) } rtac_make_voice_callback(RTAC_CVS, data->payload, data->payload_size); - } else if (data->opcode == VSS_ISTREAM_EVT_RX_DTMF_DETECTED) { + } else if (data->opcode == VSS_ISTREAM_EVT_RX_DTMF_DETECTED) { struct vss_istream_evt_rx_dtmf_detected *dtmf_rx_detected; uint32_t *voc_pkt = data->payload; uint32_t pkt_len = data->payload_size; diff --git a/sound/soc/msm/qdsp6v2/rtac.c b/sound/soc/msm/qdsp6v2/rtac.c index 5e33fb508455..82d954c646dc 100644 --- a/sound/soc/msm/qdsp6v2/rtac.c +++ b/sound/soc/msm/qdsp6v2/rtac.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -1349,7 +1349,7 @@ static int send_rtac_afe_apr(void __user *buf, uint32_t opcode) if (copy_from_user(rtac_cal[AFE_RTAC_CAL].cal_data.kvaddr, (void __user *) buf + offsetof(struct rtac_afe_user_data, - v3_get.param_hdr), + v3_set.param_hdr), payload_size)) { pr_err("%s: Could not copy payload from user buffer\n", __func__); diff --git a/sound/soc/msm/sdm660-internal.c b/sound/soc/msm/sdm660-internal.c index 259763449456..4e2a40319b02 100644 --- a/sound/soc/msm/sdm660-internal.c +++ b/sound/soc/msm/sdm660-internal.c @@ -1379,6 +1379,8 @@ static int msm_audrx_init(struct snd_soc_pcm_runtime *rtd) snd_soc_dapm_ignore_suspend(dapm, "Secondary Mic"); snd_soc_dapm_ignore_suspend(dapm, "Digital Mic1"); snd_soc_dapm_ignore_suspend(dapm, "Digital Mic2"); + snd_soc_dapm_ignore_suspend(dapm, "Digital Mic3"); + snd_soc_dapm_ignore_suspend(dapm, "Digital Mic4"); snd_soc_dapm_ignore_suspend(dapm, "EAR"); snd_soc_dapm_ignore_suspend(dapm, "HEADPHONE"); |
