diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy index 839fab811b18..db17fc8a0c9f 100644 --- a/Documentation/ABI/testing/ima_policy +++ b/Documentation/ABI/testing/ima_policy @@ -27,8 +27,9 @@ Description: [fowner=] [fgroup=]] lsm: [[subj_user=] [subj_role=] [subj_type=] [obj_user=] [obj_role=] [obj_type=]] - option: [[appraise_type=]] [template=] [permit_directio] - [appraise_flag=] [appraise_algos=] [keyrings=] + option: [digest_type=] [template=] [permit_directio] + [appraise_type=] [appraise_flag=] + [appraise_algos=] [keyrings=] base: func:= [BPRM_CHECK][MMAP_CHECK][CREDS_CHECK][FILE_CHECK][MODULE_CHECK] [FIRMWARE_CHECK] @@ -47,10 +48,21 @@ Description: fgroup:= decimal value lsm: are LSM specific option: - appraise_type:= [imasig] [imasig|modsig] + appraise_type:= [imasig] | [imasig|modsig] | [sigv3] + where 'imasig' is the original or the signature + format v2. + where 'modsig' is an appended signature, + where 'sigv3' is the signature format v3. (Currently + limited to fsverity digest based signatures + stored in security.ima xattr. Requires + specifying "digest_type=verity" first.) + appraise_flag:= [check_blacklist] Currently, blacklist check is only for files signed with appended signature. + digest_type:= verity + Require fs-verity's file digest instead of the + regular IMA file hash. keyrings:= list of keyrings (eg, .builtin_trusted_keys|.ima). Only valid when action is "measure" and func is KEY_CHECK. @@ -149,3 +161,30 @@ Description: security.ima xattr of a file: appraise func=SETXATTR_CHECK appraise_algos=sha256,sha384,sha512 + + Example of a 'measure' rule requiring fs-verity's digests + with indication of type of digest in the measurement list. + + measure func=FILE_CHECK digest_type=verity \ + template=ima-ngv2 + + Example of 'measure' and 'appraise' rules requiring fs-verity + signatures (format version 3) stored in security.ima xattr. + + The 'measure' rule specifies the 'ima-sigv3' template option, + which includes the indication of type of digest and the file + signature in the measurement list. + + measure func=BPRM_CHECK digest_type=verity \ + template=ima-sigv3 + + + The 'appraise' rule specifies the type and signature format + version (sigv3) required. + + appraise func=BPRM_CHECK digest_type=verity \ + appraise_type=sigv3 + + All of these policy rules could, for example, be constrained + either based on a filesystem's UUID (fsuuid) or based on LSM + labels. diff --git a/Documentation/ABI/testing/sysfs-class-regulator b/Documentation/ABI/testing/sysfs-class-regulator index 8516f08806dd..475b9a372657 100644 --- a/Documentation/ABI/testing/sysfs-class-regulator +++ b/Documentation/ABI/testing/sysfs-class-regulator @@ -370,3 +370,84 @@ Description: 'unknown' means software cannot determine the state, or the reported state is invalid. + +What: /sys/class/regulator/.../under_voltage +Date: April 2022 +KernelVersion: 5.18 +Contact: Zev Weiss +Description: + Some regulator directories will contain a field called + under_voltage. This indicates if the device reports an + under-voltage fault (1) or not (0). + +What: /sys/class/regulator/.../over_current +Date: April 2022 +KernelVersion: 5.18 +Contact: Zev Weiss +Description: + Some regulator directories will contain a field called + over_current. This indicates if the device reports an + over-current fault (1) or not (0). + +What: /sys/class/regulator/.../regulation_out +Date: April 2022 +KernelVersion: 5.18 +Contact: Zev Weiss +Description: + Some regulator directories will contain a field called + regulation_out. This indicates if the device reports an + out-of-regulation fault (1) or not (0). + +What: /sys/class/regulator/.../fail +Date: April 2022 +KernelVersion: 5.18 +Contact: Zev Weiss +Description: + Some regulator directories will contain a field called + fail. This indicates if the device reports an output failure + (1) or not (0). + +What: /sys/class/regulator/.../over_temp +Date: April 2022 +KernelVersion: 5.18 +Contact: Zev Weiss +Description: + Some regulator directories will contain a field called + over_temp. This indicates if the device reports an + over-temperature fault (1) or not (0). + +What: /sys/class/regulator/.../under_voltage_warn +Date: April 2022 +KernelVersion: 5.18 +Contact: Zev Weiss +Description: + Some regulator directories will contain a field called + under_voltage_warn. This indicates if the device reports an + under-voltage warning (1) or not (0). + +What: /sys/class/regulator/.../over_current_warn +Date: April 2022 +KernelVersion: 5.18 +Contact: Zev Weiss +Description: + Some regulator directories will contain a field called + over_current_warn. This indicates if the device reports an + over-current warning (1) or not (0). + +What: /sys/class/regulator/.../over_voltage_warn +Date: April 2022 +KernelVersion: 5.18 +Contact: Zev Weiss +Description: + Some regulator directories will contain a field called + over_voltage_warn. This indicates if the device reports an + over-voltage warning (1) or not (0). + +What: /sys/class/regulator/.../over_temp_warn +Date: April 2022 +KernelVersion: 5.18 +Contact: Zev Weiss +Description: + Some regulator directories will contain a field called + over_temp_warn. This indicates if the device reports an + over-temperature warning (1) or not (0). diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 7565c41ec224..bda6d084e8af 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -1922,7 +1922,8 @@ ima_template= [IMA] Select one of defined IMA measurements template formats. - Formats: { "ima" | "ima-ng" | "ima-sig" } + Formats: { "ima" | "ima-ng" | "ima-ngv2" | "ima-sig" | + "ima-sigv2" } Default: "ima-ng" ima_template_fmt= @@ -6089,11 +6090,22 @@ sources: - "tpm" - "tee" + - "caam" If not specified then it defaults to iterating through the trust source list starting with TPM and assigns the first trust source as a backend which is initialized successfully during iteration. + trusted.rng= [KEYS] + Format: + The RNG used to generate key material for trusted keys. + Can be one of: + - "kernel" + - the same value as trusted.source: "tpm" or "tee" + - "default" + If not specified, "default" is used. In this case, + the RNG's choice is left to each individual trust source. + tsc= Disable clocksource stability checks for TSC. Format: [x86] reliable: mark tsc clocksource as reliable, this diff --git a/Documentation/devicetree/bindings/devfreq/rk3399_dmc.txt b/Documentation/devicetree/bindings/devfreq/rk3399_dmc.txt deleted file mode 100644 index 58fc8a6cebc7..000000000000 --- a/Documentation/devicetree/bindings/devfreq/rk3399_dmc.txt +++ /dev/null @@ -1,212 +0,0 @@ -* Rockchip rk3399 DMC (Dynamic Memory Controller) device - -Required properties: -- compatible: Must be "rockchip,rk3399-dmc". -- devfreq-events: Node to get DDR loading, Refer to - Documentation/devicetree/bindings/devfreq/event/ - rockchip-dfi.txt -- clocks: Phandles for clock specified in "clock-names" property -- clock-names : The name of clock used by the DFI, must be - "pclk_ddr_mon"; -- operating-points-v2: Refer to Documentation/devicetree/bindings/opp/opp-v2.yaml - for details. -- center-supply: DMC supply node. -- status: Marks the node enabled/disabled. -- rockchip,pmu: Phandle to the syscon managing the "PMU general register - files". - -Optional properties: -- interrupts: The CPU interrupt number. The interrupt specifier - format depends on the interrupt controller. - It should be a DCF interrupt. When DDR DVFS finishes - a DCF interrupt is triggered. -- rockchip,pmu: Phandle to the syscon managing the "PMU general register - files". - -Following properties relate to DDR timing: - -- rockchip,dram_speed_bin : Value reference include/dt-bindings/clock/rk3399-ddr.h, - it selects the DDR3 cl-trp-trcd type. It must be - set according to "Speed Bin" in DDR3 datasheet, - DO NOT use a smaller "Speed Bin" than specified - for the DDR3 being used. - -- rockchip,pd_idle : Configure the PD_IDLE value. Defines the - power-down idle period in which memories are - placed into power-down mode if bus is idle - for PD_IDLE DFI clock cycles. - -- rockchip,sr_idle : Configure the SR_IDLE value. Defines the - self-refresh idle period in which memories are - placed into self-refresh mode if bus is idle - for SR_IDLE * 1024 DFI clock cycles (DFI - clocks freq is half of DRAM clock), default - value is "0". - -- rockchip,sr_mc_gate_idle : Defines the memory self-refresh and controller - clock gating idle period. Memories are placed - into self-refresh mode and memory controller - clock arg gating started if bus is idle for - sr_mc_gate_idle*1024 DFI clock cycles. - -- rockchip,srpd_lite_idle : Defines the self-refresh power down idle - period in which memories are placed into - self-refresh power down mode if bus is idle - for srpd_lite_idle * 1024 DFI clock cycles. - This parameter is for LPDDR4 only. - -- rockchip,standby_idle : Defines the standby idle period in which - memories are placed into self-refresh mode. - The controller, pi, PHY and DRAM clock will - be gated if bus is idle for standby_idle * DFI - clock cycles. - -- rockchip,dram_dll_dis_freq : Defines the DDR3 DLL bypass frequency in MHz. - When DDR frequency is less than DRAM_DLL_DISB_FREQ, - DDR3 DLL will be bypassed. Note: if DLL was bypassed, - the odt will also stop working. - -- rockchip,phy_dll_dis_freq : Defines the PHY dll bypass frequency in - MHz (Mega Hz). When DDR frequency is less than - DRAM_DLL_DISB_FREQ, PHY DLL will be bypassed. - Note: PHY DLL and PHY ODT are independent. - -- rockchip,ddr3_odt_dis_freq : When the DRAM type is DDR3, this parameter defines - the ODT disable frequency in MHz (Mega Hz). - when the DDR frequency is less then ddr3_odt_dis_freq, - the ODT on the DRAM side and controller side are - both disabled. - -- rockchip,ddr3_drv : When the DRAM type is DDR3, this parameter defines - the DRAM side driver strength in ohms. Default - value is 40. - -- rockchip,ddr3_odt : When the DRAM type is DDR3, this parameter defines - the DRAM side ODT strength in ohms. Default value - is 120. - -- rockchip,phy_ddr3_ca_drv : When the DRAM type is DDR3, this parameter defines - the phy side CA line (incluing command line, - address line and clock line) driver strength. - Default value is 40. - -- rockchip,phy_ddr3_dq_drv : When the DRAM type is DDR3, this parameter defines - the PHY side DQ line (including DQS/DQ/DM line) - driver strength. Default value is 40. - -- rockchip,phy_ddr3_odt : When the DRAM type is DDR3, this parameter defines - the PHY side ODT strength. Default value is 240. - -- rockchip,lpddr3_odt_dis_freq : When the DRAM type is LPDDR3, this parameter defines - then ODT disable frequency in MHz (Mega Hz). - When DDR frequency is less then ddr3_odt_dis_freq, - the ODT on the DRAM side and controller side are - both disabled. - -- rockchip,lpddr3_drv : When the DRAM type is LPDDR3, this parameter defines - the DRAM side driver strength in ohms. Default - value is 34. - -- rockchip,lpddr3_odt : When the DRAM type is LPDDR3, this parameter defines - the DRAM side ODT strength in ohms. Default value - is 240. - -- rockchip,phy_lpddr3_ca_drv : When the DRAM type is LPDDR3, this parameter defines - the PHY side CA line (including command line, - address line and clock line) driver strength. - Default value is 40. - -- rockchip,phy_lpddr3_dq_drv : When the DRAM type is LPDDR3, this parameter defines - the PHY side DQ line (including DQS/DQ/DM line) - driver strength. Default value is 40. - -- rockchip,phy_lpddr3_odt : When dram type is LPDDR3, this parameter define - the phy side odt strength, default value is 240. - -- rockchip,lpddr4_odt_dis_freq : When the DRAM type is LPDDR4, this parameter - defines the ODT disable frequency in - MHz (Mega Hz). When the DDR frequency is less then - ddr3_odt_dis_freq, the ODT on the DRAM side and - controller side are both disabled. - -- rockchip,lpddr4_drv : When the DRAM type is LPDDR4, this parameter defines - the DRAM side driver strength in ohms. Default - value is 60. - -- rockchip,lpddr4_dq_odt : When the DRAM type is LPDDR4, this parameter defines - the DRAM side ODT on DQS/DQ line strength in ohms. - Default value is 40. - -- rockchip,lpddr4_ca_odt : When the DRAM type is LPDDR4, this parameter defines - the DRAM side ODT on CA line strength in ohms. - Default value is 40. - -- rockchip,phy_lpddr4_ca_drv : When the DRAM type is LPDDR4, this parameter defines - the PHY side CA line (including command address - line) driver strength. Default value is 40. - -- rockchip,phy_lpddr4_ck_cs_drv : When the DRAM type is LPDDR4, this parameter defines - the PHY side clock line and CS line driver - strength. Default value is 80. - -- rockchip,phy_lpddr4_dq_drv : When the DRAM type is LPDDR4, this parameter defines - the PHY side DQ line (including DQS/DQ/DM line) - driver strength. Default value is 80. - -- rockchip,phy_lpddr4_odt : When the DRAM type is LPDDR4, this parameter defines - the PHY side ODT strength. Default value is 60. - -Example: - dmc_opp_table: dmc_opp_table { - compatible = "operating-points-v2"; - - opp00 { - opp-hz = /bits/ 64 <300000000>; - opp-microvolt = <900000>; - }; - opp01 { - opp-hz = /bits/ 64 <666000000>; - opp-microvolt = <900000>; - }; - }; - - dmc: dmc { - compatible = "rockchip,rk3399-dmc"; - devfreq-events = <&dfi>; - interrupts = ; - clocks = <&cru SCLK_DDRC>; - clock-names = "dmc_clk"; - operating-points-v2 = <&dmc_opp_table>; - center-supply = <&ppvar_centerlogic>; - upthreshold = <15>; - downdifferential = <10>; - rockchip,ddr3_speed_bin = <21>; - rockchip,pd_idle = <0x40>; - rockchip,sr_idle = <0x2>; - rockchip,sr_mc_gate_idle = <0x3>; - rockchip,srpd_lite_idle = <0x4>; - rockchip,standby_idle = <0x2000>; - rockchip,dram_dll_dis_freq = <300>; - rockchip,phy_dll_dis_freq = <125>; - rockchip,auto_pd_dis_freq = <666>; - rockchip,ddr3_odt_dis_freq = <333>; - rockchip,ddr3_drv = <40>; - rockchip,ddr3_odt = <120>; - rockchip,phy_ddr3_ca_drv = <40>; - rockchip,phy_ddr3_dq_drv = <40>; - rockchip,phy_ddr3_odt = <240>; - rockchip,lpddr3_odt_dis_freq = <333>; - rockchip,lpddr3_drv = <34>; - rockchip,lpddr3_odt = <240>; - rockchip,phy_lpddr3_ca_drv = <40>; - rockchip,phy_lpddr3_dq_drv = <40>; - rockchip,phy_lpddr3_odt = <240>; - rockchip,lpddr4_odt_dis_freq = <333>; - rockchip,lpddr4_drv = <60>; - rockchip,lpddr4_dq_odt = <40>; - rockchip,lpddr4_ca_odt = <40>; - rockchip,phy_lpddr4_ca_drv = <40>; - rockchip,phy_lpddr4_ck_cs_drv = <80>; - rockchip,phy_lpddr4_dq_drv = <80>; - rockchip,phy_lpddr4_odt = <60>; - }; diff --git a/Documentation/devicetree/bindings/hwmon/adt7475.yaml b/Documentation/devicetree/bindings/hwmon/adt7475.yaml index 7d9c083632b9..22beb37f1bf1 100644 --- a/Documentation/devicetree/bindings/hwmon/adt7475.yaml +++ b/Documentation/devicetree/bindings/hwmon/adt7475.yaml @@ -61,6 +61,26 @@ patternProperties: $ref: /schemas/types.yaml#/definitions/uint32 enum: [0, 1] + "adi,pin(5|10)-function": + description: | + Configures the function for pin 5 on the adi,adt7473 and adi,adt7475. Or + pin 10 on the adi,adt7476 and adi,adt7490. + $ref: /schemas/types.yaml#/definitions/string + enum: + - pwm2 + - smbalert# + + "adi,pin(9|14)-function": + description: | + Configures the function for pin 9 on the adi,adt7473 and adi,adt7475. Or + pin 14 on the adi,adt7476 and adi,adt7490 + $ref: /schemas/types.yaml#/definitions/string + enum: + - tach4 + - therm# + - smbalert# + - gpio + required: - compatible - reg @@ -79,6 +99,8 @@ examples: adi,bypass-attenuator-in0 = <1>; adi,bypass-attenuator-in1 = <0>; adi,pwm-active-state = <1 0 1>; + adi,pin10-function = "smbalert#"; + adi,pin14-function = "tach4"; }; }; diff --git a/Documentation/devicetree/bindings/hwmon/lm75.yaml b/Documentation/devicetree/bindings/hwmon/lm75.yaml index 72980d083c21..8226e3b5d028 100644 --- a/Documentation/devicetree/bindings/hwmon/lm75.yaml +++ b/Documentation/devicetree/bindings/hwmon/lm75.yaml @@ -14,6 +14,7 @@ properties: compatible: enum: - adi,adt75 + - atmel,at30ts74 - dallas,ds1775 - dallas,ds75 - dallas,ds7505 diff --git a/Documentation/devicetree/bindings/hwmon/microchip,lan966x.yaml b/Documentation/devicetree/bindings/hwmon/microchip,lan966x.yaml new file mode 100644 index 000000000000..390dd6755ff5 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/microchip,lan966x.yaml @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/microchip,lan966x.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip LAN966x Hardware Monitor + +maintainers: + - Michael Walle + +description: | + Microchip LAN966x temperature monitor and fan controller + +properties: + compatible: + enum: + - microchip,lan9668-hwmon + + reg: + items: + - description: PVT registers + - description: FAN registers + + reg-names: + items: + - const: pvt + - const: fan + + clocks: + maxItems: 1 + + '#thermal-sensor-cells': + const: 0 + +required: + - compatible + - reg + - reg-names + - clocks + +additionalProperties: false + +examples: + - | + hwmon: hwmon@e2010180 { + compatible = "microchip,lan9668-hwmon"; + reg = <0xe2010180 0xc>, + <0xe20042a8 0xc>; + reg-names = "pvt", "fan"; + clocks = <&sys_clk>; + #thermal-sensor-cells = <0>; + }; diff --git a/Documentation/devicetree/bindings/hwmon/national,lm90.yaml b/Documentation/devicetree/bindings/hwmon/national,lm90.yaml index 30db92977937..b04657849852 100644 --- a/Documentation/devicetree/bindings/hwmon/national,lm90.yaml +++ b/Documentation/devicetree/bindings/hwmon/national,lm90.yaml @@ -34,6 +34,7 @@ properties: - nxp,sa56004 - onnn,nct1008 - ti,tmp451 + - ti,tmp461 - winbond,w83l771 @@ -52,10 +53,29 @@ properties: vcc-supply: description: phandle to the regulator that provides the +VCC supply + ti,extended-range-enable: + description: Set to enable extended range temperature. + type: boolean + required: - compatible - reg +allOf: + - if: + not: + properties: + compatible: + contains: + enum: + - adi,adt7461 + - adi,adt7461a + - ti,tmp451 + - ti,tmp461 + then: + properties: + ti,extended-range-enable: false + additionalProperties: false examples: diff --git a/Documentation/devicetree/bindings/hwmon/nuvoton,nct6775.yaml b/Documentation/devicetree/bindings/hwmon/nuvoton,nct6775.yaml new file mode 100644 index 000000000000..358b262431fc --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/nuvoton,nct6775.yaml @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- + +$id: http://devicetree.org/schemas/hwmon/nuvoton,nct6775.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton NCT6775 and compatible Super I/O chips + +maintainers: + - Zev Weiss + +properties: + compatible: + enum: + - nuvoton,nct6106 + - nuvoton,nct6116 + - nuvoton,nct6775 + - nuvoton,nct6776 + - nuvoton,nct6779 + - nuvoton,nct6791 + - nuvoton,nct6792 + - nuvoton,nct6793 + - nuvoton,nct6795 + - nuvoton,nct6796 + - nuvoton,nct6797 + - nuvoton,nct6798 + + reg: + maxItems: 1 + + nuvoton,tsi-channel-mask: + description: + Bitmask indicating which TSI temperature sensor channels are + active. LSB is TSI0, bit 1 is TSI1, etc. + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 0xff + default: 0 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + superio@4d { + compatible = "nuvoton,nct6779"; + reg = <0x4d>; + nuvoton,tsi-channel-mask = <0x03>; + }; + }; diff --git a/Documentation/devicetree/bindings/hwmon/ti,tmp401.yaml b/Documentation/devicetree/bindings/hwmon/ti,tmp401.yaml new file mode 100644 index 000000000000..fe0ac08faa1a --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/ti,tmp401.yaml @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/ti,tmp401.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TMP401, TPM411 and TMP43x temperature sensor + +maintainers: + - Guenter Roeck + +description: | + ±1°C Remote and Local temperature sensor + + Datasheets: + https://www.ti.com/lit/ds/symlink/tmp401.pdf + https://www.ti.com/lit/ds/symlink/tmp411.pdf + https://www.ti.com/lit/ds/symlink/tmp431.pdf + https://www.ti.com/lit/ds/symlink/tmp435.pdf + +properties: + compatible: + enum: + - ti,tmp401 + - ti,tmp411 + - ti,tmp431 + - ti,tmp432 + - ti,tmp435 + + reg: + maxItems: 1 + + ti,extended-range-enable: + description: + When set, this sensor measures over extended temperature range. + type: boolean + + ti,n-factor: + description: + value to be used for converting remote channel measurements to + temperature. + $ref: /schemas/types.yaml#/definitions/int32 + items: + minimum: -128 + maximum: 127 + + ti,beta-compensation: + description: + value to select beta correction range. + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 15 + +allOf: + - if: + properties: + compatible: + contains: + enum: + - ti,tmp401 + then: + properties: + ti,n-factor: false + + - if: + properties: + compatible: + contains: + enum: + - ti,tmp401 + - ti,tmp411 + then: + properties: + ti,beta-compensation: false + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + sensor@4c { + compatible = "ti,tmp401"; + reg = <0x4c>; + }; + }; + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + sensor@4c { + compatible = "ti,tmp431"; + reg = <0x4c>; + ti,extended-range-enable; + ti,n-factor = <0x3b>; + ti,beta-compensation = <0x7>; + }; + }; diff --git a/Documentation/devicetree/bindings/memory-controllers/rockchip,rk3399-dmc.yaml b/Documentation/devicetree/bindings/memory-controllers/rockchip,rk3399-dmc.yaml new file mode 100644 index 000000000000..fb4920397d08 --- /dev/null +++ b/Documentation/devicetree/bindings/memory-controllers/rockchip,rk3399-dmc.yaml @@ -0,0 +1,384 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# %YAML 1.2 +--- +$id: http://devicetree.org/schemas/memory-controllers/rockchip,rk3399-dmc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip rk3399 DMC (Dynamic Memory Controller) device + +maintainers: + - Brian Norris + +properties: + compatible: + enum: + - rockchip,rk3399-dmc + + devfreq-events: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Node to get DDR loading. Refer to + Documentation/devicetree/bindings/devfreq/event/rockchip-dfi.txt. + + clocks: + maxItems: 1 + + clock-names: + items: + - const: dmc_clk + + operating-points-v2: true + + center-supply: + description: + DMC regulator supply. + + rockchip,pmu: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Phandle to the syscon managing the "PMU general register files". + + interrupts: + maxItems: 1 + description: + The CPU interrupt number. It should be a DCF interrupt. When DDR DVFS + finishes, a DCF interrupt is triggered. + + rockchip,ddr3_speed_bin: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + For values, reference include/dt-bindings/clock/rk3399-ddr.h. Selects the + DDR3 cl-trp-trcd type. It must be set according to "Speed Bin" in DDR3 + datasheet; DO NOT use a smaller "Speed Bin" than specified for the DDR3 + being used. + + rockchip,pd_idle: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Configure the PD_IDLE value. Defines the power-down idle period in which + memories are placed into power-down mode if bus is idle for PD_IDLE DFI + clock cycles. + See also rockchip,pd-idle-ns. + + rockchip,sr_idle: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Configure the SR_IDLE value. Defines the self-refresh idle period in + which memories are placed into self-refresh mode if bus is idle for + SR_IDLE * 1024 DFI clock cycles (DFI clocks freq is half of DRAM clock). + See also rockchip,sr-idle-ns. + default: 0 + + rockchip,sr_mc_gate_idle: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Defines the memory self-refresh and controller clock gating idle period. + Memories are placed into self-refresh mode and memory controller clock + arg gating started if bus is idle for sr_mc_gate_idle*1024 DFI clock + cycles. + See also rockchip,sr-mc-gate-idle-ns. + + rockchip,srpd_lite_idle: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Defines the self-refresh power down idle period in which memories are + placed into self-refresh power down mode if bus is idle for + srpd_lite_idle * 1024 DFI clock cycles. This parameter is for LPDDR4 + only. + See also rockchip,srpd-lite-idle-ns. + + rockchip,standby_idle: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Defines the standby idle period in which memories are placed into + self-refresh mode. The controller, pi, PHY and DRAM clock will be gated + if bus is idle for standby_idle * DFI clock cycles. + See also rockchip,standby-idle-ns. + + rockchip,dram_dll_dis_freq: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + Defines the DDR3 DLL bypass frequency in MHz. When DDR frequency is less + than DRAM_DLL_DISB_FREQ, DDR3 DLL will be bypassed. + Note: if DLL was bypassed, the odt will also stop working. + + rockchip,phy_dll_dis_freq: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + Defines the PHY dll bypass frequency in MHz (Mega Hz). When DDR frequency + is less than DRAM_DLL_DISB_FREQ, PHY DLL will be bypassed. + Note: PHY DLL and PHY ODT are independent. + + rockchip,auto_pd_dis_freq: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Defines the auto PD disable frequency in MHz. + + rockchip,ddr3_odt_dis_freq: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1000000 # In case anyone thought this was MHz. + description: + When the DRAM type is DDR3, this parameter defines the ODT disable + frequency in Hz. When the DDR frequency is less then ddr3_odt_dis_freq, + the ODT on the DRAM side and controller side are both disabled. + + rockchip,ddr3_drv: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When the DRAM type is DDR3, this parameter defines the DRAM side drive + strength in ohms. + default: 40 + + rockchip,ddr3_odt: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When the DRAM type is DDR3, this parameter defines the DRAM side ODT + strength in ohms. + default: 120 + + rockchip,phy_ddr3_ca_drv: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When the DRAM type is DDR3, this parameter defines the phy side CA line + (incluing command line, address line and clock line) drive strength. + default: 40 + + rockchip,phy_ddr3_dq_drv: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When the DRAM type is DDR3, this parameter defines the PHY side DQ line + (including DQS/DQ/DM line) drive strength. + default: 40 + + rockchip,phy_ddr3_odt: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When the DRAM type is DDR3, this parameter defines the PHY side ODT + strength. + default: 240 + + rockchip,lpddr3_odt_dis_freq: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1000000 # In case anyone thought this was MHz. + description: + When the DRAM type is LPDDR3, this parameter defines then ODT disable + frequency in Hz. When DDR frequency is less then ddr3_odt_dis_freq, the + ODT on the DRAM side and controller side are both disabled. + + rockchip,lpddr3_drv: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When the DRAM type is LPDDR3, this parameter defines the DRAM side drive + strength in ohms. + default: 34 + + rockchip,lpddr3_odt: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When the DRAM type is LPDDR3, this parameter defines the DRAM side ODT + strength in ohms. + default: 240 + + rockchip,phy_lpddr3_ca_drv: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When the DRAM type is LPDDR3, this parameter defines the PHY side CA line + (including command line, address line and clock line) drive strength. + default: 40 + + rockchip,phy_lpddr3_dq_drv: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When the DRAM type is LPDDR3, this parameter defines the PHY side DQ line + (including DQS/DQ/DM line) drive strength. + default: 40 + + rockchip,phy_lpddr3_odt: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When dram type is LPDDR3, this parameter define the phy side odt + strength, default value is 240. + + rockchip,lpddr4_odt_dis_freq: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1000000 # In case anyone thought this was MHz. + description: + When the DRAM type is LPDDR4, this parameter defines the ODT disable + frequency in Hz. When the DDR frequency is less then ddr3_odt_dis_freq, + the ODT on the DRAM side and controller side are both disabled. + + rockchip,lpddr4_drv: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When the DRAM type is LPDDR4, this parameter defines the DRAM side drive + strength in ohms. + default: 60 + + rockchip,lpddr4_dq_odt: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When the DRAM type is LPDDR4, this parameter defines the DRAM side ODT on + DQS/DQ line strength in ohms. + default: 40 + + rockchip,lpddr4_ca_odt: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When the DRAM type is LPDDR4, this parameter defines the DRAM side ODT on + CA line strength in ohms. + default: 40 + + rockchip,phy_lpddr4_ca_drv: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When the DRAM type is LPDDR4, this parameter defines the PHY side CA line + (including command address line) drive strength. + default: 40 + + rockchip,phy_lpddr4_ck_cs_drv: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When the DRAM type is LPDDR4, this parameter defines the PHY side clock + line and CS line drive strength. + default: 80 + + rockchip,phy_lpddr4_dq_drv: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When the DRAM type is LPDDR4, this parameter defines the PHY side DQ line + (including DQS/DQ/DM line) drive strength. + default: 80 + + rockchip,phy_lpddr4_odt: + deprecated: true + $ref: /schemas/types.yaml#/definitions/uint32 + description: + When the DRAM type is LPDDR4, this parameter defines the PHY side ODT + strength. + default: 60 + + rockchip,pd-idle-ns: + description: + Configure the PD_IDLE value in nanoseconds. Defines the power-down idle + period in which memories are placed into power-down mode if bus is idle + for PD_IDLE nanoseconds. + + rockchip,sr-idle-ns: + description: + Configure the SR_IDLE value in nanoseconds. Defines the self-refresh idle + period in which memories are placed into self-refresh mode if bus is idle + for SR_IDLE nanoseconds. + default: 0 + + rockchip,sr-mc-gate-idle-ns: + description: + Defines the memory self-refresh and controller clock gating idle period in nanoseconds. + Memories are placed into self-refresh mode and memory controller clock + arg gating started if bus is idle for sr_mc_gate_idle nanoseconds. + + rockchip,srpd-lite-idle-ns: + description: + Defines the self-refresh power down idle period in which memories are + placed into self-refresh power down mode if bus is idle for + srpd_lite_idle nanoseonds. This parameter is for LPDDR4 only. + + rockchip,standby-idle-ns: + description: + Defines the standby idle period in which memories are placed into + self-refresh mode. The controller, pi, PHY and DRAM clock will be gated + if bus is idle for standby_idle nanoseconds. + + rockchip,pd-idle-dis-freq-hz: + description: + Defines the power-down idle disable frequency in Hz. When the DDR + frequency is greater than pd-idle-dis-freq, power-down idle is disabled. + See also rockchip,pd-idle-ns. + + rockchip,sr-idle-dis-freq-hz: + description: + Defines the self-refresh idle disable frequency in Hz. When the DDR + frequency is greater than sr-idle-dis-freq, self-refresh idle is + disabled. See also rockchip,sr-idle-ns. + + rockchip,sr-mc-gate-idle-dis-freq-hz: + description: + Defines the self-refresh and memory-controller clock gating disable + frequency in Hz. When the DDR frequency is greater than + sr-mc-gate-idle-dis-freq, the clock will not be gated when idle. See also + rockchip,sr-mc-gate-idle-ns. + + rockchip,srpd-lite-idle-dis-freq-hz: + description: + Defines the self-refresh power down idle disable frequency in Hz. When + the DDR frequency is greater than srpd-lite-idle-dis-freq, memory will + not be placed into self-refresh power down mode when idle. See also + rockchip,srpd-lite-idle-ns. + + rockchip,standby-idle-dis-freq-hz: + description: + Defines the standby idle disable frequency in Hz. When the DDR frequency + is greater than standby-idle-dis-freq, standby idle is disabled. See also + rockchip,standby-idle-ns. + +required: + - compatible + - devfreq-events + - clocks + - clock-names + - operating-points-v2 + - center-supply + +additionalProperties: false + +examples: + - | + #include + #include + memory-controller { + compatible = "rockchip,rk3399-dmc"; + devfreq-events = <&dfi>; + rockchip,pmu = <&pmu>; + interrupts = ; + clocks = <&cru SCLK_DDRC>; + clock-names = "dmc_clk"; + operating-points-v2 = <&dmc_opp_table>; + center-supply = <&ppvar_centerlogic>; + rockchip,pd-idle-ns = <160>; + rockchip,sr-idle-ns = <10240>; + rockchip,sr-mc-gate-idle-ns = <40960>; + rockchip,srpd-lite-idle-ns = <61440>; + rockchip,standby-idle-ns = <81920>; + rockchip,ddr3_odt_dis_freq = <333000000>; + rockchip,lpddr3_odt_dis_freq = <333000000>; + rockchip,lpddr4_odt_dis_freq = <333000000>; + rockchip,pd-idle-dis-freq-hz = <1000000000>; + rockchip,sr-idle-dis-freq-hz = <1000000000>; + rockchip,sr-mc-gate-idle-dis-freq-hz = <1000000000>; + rockchip,srpd-lite-idle-dis-freq-hz = <0>; + rockchip,standby-idle-dis-freq-hz = <928000000>; + }; diff --git a/Documentation/devicetree/bindings/mmc/brcm,sdhci-brcmstb.yaml b/Documentation/devicetree/bindings/mmc/brcm,sdhci-brcmstb.yaml index dccd5ad96981..b672202fff4e 100644 --- a/Documentation/devicetree/bindings/mmc/brcm,sdhci-brcmstb.yaml +++ b/Documentation/devicetree/bindings/mmc/brcm,sdhci-brcmstb.yaml @@ -31,7 +31,7 @@ properties: - const: brcm,sdhci-brcmstb reg: - minItems: 2 + maxItems: 2 reg-names: items: @@ -65,15 +65,15 @@ unevaluatedProperties: false examples: - | mmc@84b0000 { - sd-uhs-sdr50; - sd-uhs-ddr50; - sd-uhs-sdr104; - sdhci,auto-cmd12; compatible = "brcm,bcm7216-sdhci", "brcm,bcm7445-sdhci", "brcm,sdhci-brcmstb"; reg = <0x84b0000 0x260>, <0x84b0300 0x200>; reg-names = "host", "cfg"; + sd-uhs-sdr50; + sd-uhs-ddr50; + sd-uhs-sdr104; + sdhci,auto-cmd12; interrupts = <0x0 0x26 0x4>; interrupt-names = "sdio0_0"; clocks = <&scmi_clk 245>; @@ -81,6 +81,11 @@ examples: }; mmc@84b1000 { + compatible = "brcm,bcm7216-sdhci", + "brcm,bcm7445-sdhci", + "brcm,sdhci-brcmstb"; + reg = <0x84b1000 0x260>, <0x84b1300 0x200>; + reg-names = "host", "cfg"; mmc-ddr-1_8v; mmc-hs200-1_8v; mmc-hs400-1_8v; @@ -88,11 +93,6 @@ examples: supports-cqe; non-removable; bus-width = <0x8>; - compatible = "brcm,bcm7216-sdhci", - "brcm,bcm7445-sdhci", - "brcm,sdhci-brcmstb"; - reg = <0x84b1000 0x260>, <0x84b1300 0x200>; - reg-names = "host", "cfg"; interrupts = <0x0 0x27 0x4>; interrupt-names = "sdio1_0"; clocks = <&scmi_clk 245>; diff --git a/Documentation/devicetree/bindings/mmc/fsl-imx-esdhc.yaml b/Documentation/devicetree/bindings/mmc/fsl-imx-esdhc.yaml index 7dbbcae9485c..29339d0196ec 100644 --- a/Documentation/devicetree/bindings/mmc/fsl-imx-esdhc.yaml +++ b/Documentation/devicetree/bindings/mmc/fsl-imx-esdhc.yaml @@ -34,22 +34,47 @@ properties: - fsl,imx6ull-usdhc - fsl,imx7d-usdhc - fsl,imx7ulp-usdhc + - fsl,imx8mm-usdhc - fsl,imxrt1050-usdhc - nxp,s32g2-usdhc - items: - enum: - - fsl,imx8mm-usdhc - - fsl,imx8mn-usdhc - - fsl,imx8mp-usdhc - fsl,imx8mq-usdhc - - fsl,imx8qm-usdhc - - fsl,imx8qxp-usdhc - const: fsl,imx7d-usdhc - items: - enum: + - fsl,imx8mn-usdhc + - fsl,imx8mp-usdhc - fsl,imx93-usdhc - fsl,imx8ulp-usdhc - const: fsl,imx8mm-usdhc + - items: + - enum: + - fsl,imx8qm-usdhc + - const: fsl,imx8qxp-usdhc + - items: + - enum: + - fsl,imx8dxl-usdhc + - fsl,imx8mm-usdhc + - fsl,imx8mn-usdhc + - fsl,imx8mp-usdhc + - fsl,imx8qm-usdhc + - fsl,imx8qxp-usdhc + - const: fsl,imx7d-usdhc + deprecated: true + - items: + - enum: + - fsl,imx8mn-usdhc + - fsl,imx8mp-usdhc + - const: fsl,imx8mm-usdhc + - const: fsl,imx7d-usdhc + deprecated: true + - items: + - enum: + - fsl,imx8qm-usdhc + - const: fsl,imx8qxp-usdhc + - const: fsl,imx7d-usdhc + deprecated: true reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/mmc/marvell,dove-sdhci.yaml b/Documentation/devicetree/bindings/mmc/marvell,dove-sdhci.yaml new file mode 100644 index 000000000000..7c9c652ad59c --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/marvell,dove-sdhci.yaml @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mmc/marvell,dove-sdhci.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Marvell sdhci-dove controller + +maintainers: + - Adrian Hunter + - Ulf Hansson + +allOf: + - $ref: mmc-controller.yaml# + +properties: + compatible: + const: marvell,dove-sdhci + + reg: + maxItems: 1 + + interrupts: + minItems: 1 + maxItems: 2 + + clocks: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + +unevaluatedProperties: false + +examples: + - | + sdio0: mmc@92000 { + compatible = "marvell,dove-sdhci"; + reg = <0x92000 0x100>; + interrupts = <35>; + clocks = <&gate_clk 9>; + }; diff --git a/Documentation/devicetree/bindings/mmc/marvell,orion-sdio.yaml b/Documentation/devicetree/bindings/mmc/marvell,orion-sdio.yaml new file mode 100644 index 000000000000..8a97ded15aed --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/marvell,orion-sdio.yaml @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mmc/marvell,orion-sdio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Marvell orion-sdio controller + +maintainers: + - Nicolas Pitre + - Ulf Hansson + +allOf: + - $ref: mmc-controller.yaml# + +properties: + compatible: + const: marvell,orion-sdio + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - clocks + +unevaluatedProperties: false + +examples: + - | + mmc@d00d4000 { + compatible = "marvell,orion-sdio"; + reg = <0xd00d4000 0x200>; + interrupts = <54>; + clocks = <&gateclk 17>; + }; diff --git a/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt deleted file mode 100644 index c51a62d751dc..000000000000 --- a/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt +++ /dev/null @@ -1,173 +0,0 @@ -Marvell Xenon SDHCI Controller device tree bindings -This file documents differences between the core mmc properties -described by mmc.txt and the properties used by the Xenon implementation. - -Multiple SDHCs might be put into a single Xenon IP, to save size and cost. -Each SDHC is independent and owns independent resources, such as register sets, -clock and PHY. -Each SDHC should have an independent device tree node. - -Required Properties: -- compatible: should be one of the following - - "marvell,armada-3700-sdhci": For controllers on Armada-3700 SoC. - Must provide a second register area and marvell,pad-type. - - "marvell,armada-ap806-sdhci": For controllers on Armada AP806. - - "marvell,armada-ap807-sdhci": For controllers on Armada AP807. - - "marvell,armada-cp110-sdhci": For controllers on Armada CP110. - -- clocks: - Array of clocks required for SDHC. - Require at least input clock for Xenon IP core. For Armada AP806 and - CP110, the AXI clock is also mandatory. - -- clock-names: - Array of names corresponding to clocks property. - The input clock for Xenon IP core should be named as "core". - The input clock for the AXI bus must be named as "axi". - -- reg: - * For "marvell,armada-3700-sdhci", two register areas. - The first one for Xenon IP register. The second one for the Armada 3700 SoC - PHY PAD Voltage Control register. - Please follow the examples with compatible "marvell,armada-3700-sdhci" - in below. - Please also check property marvell,pad-type in below. - - * For other compatible strings, one register area for Xenon IP. - -Optional Properties: -- marvell,xenon-sdhc-id: - Indicate the corresponding bit index of current SDHC in - SDHC System Operation Control Register Bit[7:0]. - Set/clear the corresponding bit to enable/disable current SDHC. - If Xenon IP contains only one SDHC, this property is optional. - -- marvell,xenon-phy-type: - Xenon support multiple types of PHYs. - To select eMMC 5.1 PHY, set: - marvell,xenon-phy-type = "emmc 5.1 phy" - eMMC 5.1 PHY is the default choice if this property is not provided. - To select eMMC 5.0 PHY, set: - marvell,xenon-phy-type = "emmc 5.0 phy" - - All those types of PHYs can support eMMC, SD and SDIO. - Please note that this property only presents the type of PHY. - It doesn't stand for the entire SDHC type or property. - For example, "emmc 5.1 phy" doesn't mean that this Xenon SDHC only - supports eMMC 5.1. - -- marvell,xenon-phy-znr: - Set PHY ZNR value. - Only available for eMMC PHY. - Valid range = [0:0x1F]. - ZNR is set as 0xF by default if this property is not provided. - -- marvell,xenon-phy-zpr: - Set PHY ZPR value. - Only available for eMMC PHY. - Valid range = [0:0x1F]. - ZPR is set as 0xF by default if this property is not provided. - -- marvell,xenon-phy-nr-success-tun: - Set the number of required consecutive successful sampling points - used to identify a valid sampling window, in tuning process. - Valid range = [1:7]. - Set as 0x4 by default if this property is not provided. - -- marvell,xenon-phy-tun-step-divider: - Set the divider for calculating TUN_STEP. - Set as 64 by default if this property is not provided. - -- marvell,xenon-phy-slow-mode: - If this property is selected, transfers will bypass PHY. - Only available when bus frequency lower than 55MHz in SDR mode. - Disabled by default. Please only try this property if timing issues - always occur with PHY enabled in eMMC HS SDR, SD SDR12, SD SDR25, - SD Default Speed and HS mode and eMMC legacy speed mode. - -- marvell,xenon-tun-count: - Xenon SDHC SoC usually doesn't provide re-tuning counter in - Capabilities Register 3 Bit[11:8]. - This property provides the re-tuning counter. - If this property is not set, default re-tuning counter will - be set as 0x9 in driver. - -- marvell,pad-type: - Type of Armada 3700 SoC PHY PAD Voltage Controller register. - Only valid when "marvell,armada-3700-sdhci" is selected. - Two types: "sd" and "fixed-1-8v". - If "sd" is selected, SoC PHY PAD is set as 3.3V at the beginning and is - switched to 1.8V when later in higher speed mode. - If "fixed-1-8v" is selected, SoC PHY PAD is fixed 1.8V, such as for eMMC. - Please follow the examples with compatible "marvell,armada-3700-sdhci" - in below. - -Example: -- For eMMC: - - sdhci@aa0000 { - compatible = "marvell,armada-ap806-sdhci"; - reg = <0xaa0000 0x1000>; - interrupts = - clocks = <&emmc_clk>,<&axi_clk>; - clock-names = "core", "axi"; - bus-width = <4>; - marvell,xenon-phy-slow-mode; - marvell,xenon-tun-count = <11>; - non-removable; - no-sd; - no-sdio; - - /* Vmmc and Vqmmc are both fixed */ - }; - -- For SD/SDIO: - - sdhci@ab0000 { - compatible = "marvell,armada-cp110-sdhci"; - reg = <0xab0000 0x1000>; - interrupts = - vqmmc-supply = <&sd_vqmmc_regulator>; - vmmc-supply = <&sd_vmmc_regulator>; - clocks = <&sdclk>, <&axi_clk>; - clock-names = "core", "axi"; - bus-width = <4>; - marvell,xenon-tun-count = <9>; - }; - -- For eMMC with compatible "marvell,armada-3700-sdhci": - - sdhci@aa0000 { - compatible = "marvell,armada-3700-sdhci"; - reg = <0xaa0000 0x1000>, - ; - interrupts = - clocks = <&emmcclk>; - clock-names = "core"; - bus-width = <8>; - mmc-ddr-1_8v; - mmc-hs400-1_8v; - non-removable; - no-sd; - no-sdio; - - /* Vmmc and Vqmmc are both fixed */ - - marvell,pad-type = "fixed-1-8v"; - }; - -- For SD/SDIO with compatible "marvell,armada-3700-sdhci": - - sdhci@ab0000 { - compatible = "marvell,armada-3700-sdhci"; - reg = <0xab0000 0x1000>, - ; - interrupts = - vqmmc-supply = <&sd_regulator>; - /* Vmmc is fixed */ - clocks = <&sdclk>; - clock-names = "core"; - bus-width = <4>; - - marvell,pad-type = "sd"; - }; diff --git a/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.yaml b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.yaml new file mode 100644 index 000000000000..c79639e9027e --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.yaml @@ -0,0 +1,275 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mmc/marvell,xenon-sdhci.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Marvell Xenon SDHCI Controller + +description: | + This file documents differences between the core MMC properties described by + mmc-controller.yaml and the properties used by the Xenon implementation. + + Multiple SDHCs might be put into a single Xenon IP, to save size and cost. + Each SDHC is independent and owns independent resources, such as register + sets, clock and PHY. + + Each SDHC should have an independent device tree node. + +maintainers: + - Ulf Hansson + +properties: + compatible: + oneOf: + - enum: + - marvell,armada-cp110-sdhci + - marvell,armada-ap806-sdhci + + - items: + - const: marvell,armada-ap807-sdhci + - const: marvell,armada-ap806-sdhci + + - items: + - const: marvell,armada-3700-sdhci + - const: marvell,sdhci-xenon + + reg: + minItems: 1 + maxItems: 2 + description: | + For "marvell,armada-3700-sdhci", two register areas. The first one + for Xenon IP register. The second one for the Armada 3700 SoC PHY PAD + Voltage Control register. Please follow the examples with compatible + "marvell,armada-3700-sdhci" in below. + Please also check property marvell,pad-type in below. + + For other compatible strings, one register area for Xenon IP. + + clocks: + minItems: 1 + maxItems: 2 + + clock-names: + minItems: 1 + items: + - const: core + - const: axi + + marvell,xenon-sdhc-id: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 7 + description: | + Indicate the corresponding bit index of current SDHC in SDHC System + Operation Control Register Bit[7:0]. Set/clear the corresponding bit to + enable/disable current SDHC. + + marvell,xenon-phy-type: + $ref: /schemas/types.yaml#/definitions/string + enum: + - "emmc 5.1 phy" + - "emmc 5.0 phy" + description: | + Xenon support multiple types of PHYs. To select eMMC 5.1 PHY, set: + marvell,xenon-phy-type = "emmc 5.1 phy" eMMC 5.1 PHY is the default + choice if this property is not provided. To select eMMC 5.0 PHY, set: + marvell,xenon-phy-type = "emmc 5.0 phy" + + All those types of PHYs can support eMMC, SD and SDIO. Please note that + this property only presents the type of PHY. It doesn't stand for the + entire SDHC type or property. For example, "emmc 5.1 phy" doesn't mean + that this Xenon SDHC only supports eMMC 5.1. + + marvell,xenon-phy-znr: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 0x1f + default: 0xf + description: | + Set PHY ZNR value. + Only available for eMMC PHY. + + marvell,xenon-phy-zpr: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 0x1f + default: 0xf + description: | + Set PHY ZPR value. + Only available for eMMC PHY. + + marvell,xenon-phy-nr-success-tun: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 7 + default: 0x4 + description: | + Set the number of required consecutive successful sampling points + used to identify a valid sampling window, in tuning process. + + marvell,xenon-phy-tun-step-divider: + $ref: /schemas/types.yaml#/definitions/uint32 + default: 64 + description: | + Set the divider for calculating TUN_STEP. + + marvell,xenon-phy-slow-mode: + type: boolean + description: | + If this property is selected, transfers will bypass PHY. + Only available when bus frequency lower than 55MHz in SDR mode. + Disabled by default. Please only try this property if timing issues + always occur with PHY enabled in eMMC HS SDR, SD SDR12, SD SDR25, + SD Default Speed and HS mode and eMMC legacy speed mode. + + marvell,xenon-tun-count: + $ref: /schemas/types.yaml#/definitions/uint32 + default: 0x9 + description: | + Xenon SDHC SoC usually doesn't provide re-tuning counter in + Capabilities Register 3 Bit[11:8]. + This property provides the re-tuning counter. + +allOf: + - $ref: mmc-controller.yaml# + - if: + properties: + compatible: + contains: + const: marvell,armada-3700-sdhci + + then: + properties: + reg: + items: + - description: Xenon IP registers + - description: Armada 3700 SoC PHY PAD Voltage Control register + minItems: 2 + + marvell,pad-type: + $ref: /schemas/types.yaml#/definitions/string + enum: + - sd + - fixed-1-8v + description: | + Type of Armada 3700 SoC PHY PAD Voltage Controller register. + If "sd" is selected, SoC PHY PAD is set as 3.3V at the beginning + and is switched to 1.8V when later in higher speed mode. + If "fixed-1-8v" is selected, SoC PHY PAD is fixed 1.8V, such as for + eMMC. + Please follow the examples with compatible + "marvell,armada-3700-sdhci" in below. + + required: + - marvell,pad-type + + - if: + properties: + compatible: + contains: + enum: + - marvell,armada-cp110-sdhci + - marvell,armada-ap807-sdhci + - marvell,armada-ap806-sdhci + + then: + properties: + clocks: + minItems: 2 + + clock-names: + items: + - const: core + - const: axi + + +required: + - compatible + - reg + - clocks + - clock-names + +unevaluatedProperties: false + +examples: + - | + // For eMMC + #include + #include + + mmc@aa0000 { + compatible = "marvell,armada-ap807-sdhci", "marvell,armada-ap806-sdhci"; + reg = <0xaa0000 0x1000>; + interrupts = ; + clocks = <&emmc_clk 0>, <&axi_clk 0>; + clock-names = "core", "axi"; + bus-width = <4>; + marvell,xenon-phy-slow-mode; + marvell,xenon-tun-count = <11>; + non-removable; + no-sd; + no-sdio; + + /* Vmmc and Vqmmc are both fixed */ + }; + + - | + // For SD/SDIO + #include + #include + + mmc@ab0000 { + compatible = "marvell,armada-cp110-sdhci"; + reg = <0xab0000 0x1000>; + interrupts = ; + vqmmc-supply = <&sd_vqmmc_regulator>; + vmmc-supply = <&sd_vmmc_regulator>; + clocks = <&sdclk 0>, <&axi_clk 0>; + clock-names = "core", "axi"; + bus-width = <4>; + marvell,xenon-tun-count = <9>; + }; + + - | + // For eMMC with compatible "marvell,armada-3700-sdhci": + #include + #include + + mmc@aa0000 { + compatible = "marvell,armada-3700-sdhci", "marvell,sdhci-xenon"; + reg = <0xaa0000 0x1000>, + <0x17808 0x4>; + interrupts = ; + clocks = <&emmcclk 0>; + clock-names = "core"; + bus-width = <8>; + mmc-ddr-1_8v; + mmc-hs400-1_8v; + non-removable; + no-sd; + no-sdio; + + /* Vmmc and Vqmmc are both fixed */ + + marvell,pad-type = "fixed-1-8v"; + }; + + - | + // For SD/SDIO with compatible "marvell,armada-3700-sdhci": + #include + #include + + mmc@ab0000 { + compatible = "marvell,armada-3700-sdhci", "marvell,sdhci-xenon"; + reg = <0xab0000 0x1000>, + <0x17808 0x4>; + interrupts = ; + vqmmc-supply = <&sd_regulator>; + /* Vmmc is fixed */ + clocks = <&sdclk 0>; + clock-names = "core"; + bus-width = <4>; + + marvell,pad-type = "sd"; + }; diff --git a/Documentation/devicetree/bindings/mmc/mmc-controller.yaml b/Documentation/devicetree/bindings/mmc/mmc-controller.yaml index 513f3c8758aa..ff5ce89e5111 100644 --- a/Documentation/devicetree/bindings/mmc/mmc-controller.yaml +++ b/Documentation/devicetree/bindings/mmc/mmc-controller.yaml @@ -298,7 +298,10 @@ properties: vqmmc-supply: description: - Supply for the bus IO line power + Supply for the bus IO line power, such as a level shifter. + If the level shifter is controlled by a GPIO line, this shall + be modeled as a "regulator-fixed" with a GPIO line for + switching the level shifter on/off. mmc-pwrseq: $ref: /schemas/types.yaml#/definitions/phandle diff --git a/Documentation/devicetree/bindings/mmc/mtk-sd.yaml b/Documentation/devicetree/bindings/mmc/mtk-sd.yaml index 297ada03e3de..2a2e9fa8c188 100644 --- a/Documentation/devicetree/bindings/mmc/mtk-sd.yaml +++ b/Documentation/devicetree/bindings/mmc/mtk-sd.yaml @@ -40,7 +40,10 @@ properties: - const: mediatek,mt8183-mmc reg: - maxItems: 1 + minItems: 1 + items: + - description: base register (required). + - description: top base register (required for MT8183). clocks: description: @@ -168,6 +171,16 @@ required: - vmmc-supply - vqmmc-supply +if: + properties: + compatible: + contains: + const: mediatek,mt8183-mmc +then: + properties: + reg: + minItems: 2 + unevaluatedProperties: false examples: diff --git a/Documentation/devicetree/bindings/mmc/orion-sdio.txt b/Documentation/devicetree/bindings/mmc/orion-sdio.txt deleted file mode 100644 index 10f0818a34c5..000000000000 --- a/Documentation/devicetree/bindings/mmc/orion-sdio.txt +++ /dev/null @@ -1,16 +0,0 @@ -* Marvell orion-sdio controller - -This file documents differences between the core properties in mmc.txt -and the properties used by the orion-sdio driver. - -- compatible: Should be "marvell,orion-sdio" -- clocks: reference to the clock of the SDIO interface - -Example: - - mvsdio@d00d4000 { - compatible = "marvell,orion-sdio"; - reg = <0xd00d4000 0x200>; - interrupts = <54>; - clocks = <&gateclk 17>; - }; diff --git a/Documentation/devicetree/bindings/mmc/sdhci-am654.yaml b/Documentation/devicetree/bindings/mmc/sdhci-am654.yaml index 0566493c4def..0ab07759b472 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-am654.yaml +++ b/Documentation/devicetree/bindings/mmc/sdhci-am654.yaml @@ -186,6 +186,13 @@ properties: description: Clock Delay Buffer Select $ref: "/schemas/types.yaml#/definitions/uint32" + ti,fails-without-test-cd: + $ref: /schemas/types.yaml#/definitions/flag + description: + When present, indicates that the CD line is not connected + and the controller is required to be forced into Test mode + to set the TESTCD bit. + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/mmc/sdhci-dove.txt b/Documentation/devicetree/bindings/mmc/sdhci-dove.txt deleted file mode 100644 index ae9aab9abcd7..000000000000 --- a/Documentation/devicetree/bindings/mmc/sdhci-dove.txt +++ /dev/null @@ -1,14 +0,0 @@ -* Marvell sdhci-dove controller - -This file documents differences between the core properties in mmc.txt -and the properties used by the sdhci-pxav2 and sdhci-pxav3 drivers. - -- compatible: Should be "marvell,dove-sdhci". - -Example: - -sdio0: sdio@92000 { - compatible = "marvell,dove-sdhci"; - reg = <0x92000 0x100>; - interrupts = <35>; -}; diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt deleted file mode 100644 index 6216ed777343..000000000000 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ /dev/null @@ -1,123 +0,0 @@ -* Qualcomm SDHCI controller (sdhci-msm) - -This file documents differences between the core properties in mmc.txt -and the properties used by the sdhci-msm driver. - -Required properties: -- compatible: Should contain a SoC-specific string and a IP version string: - version strings: - "qcom,sdhci-msm-v4" for sdcc versions less than 5.0 - "qcom,sdhci-msm-v5" for sdcc version 5.0 - For SDCC version 5.0.0, MCI registers are removed from SDCC - interface and some registers are moved to HC. New compatible - string is added to support this change - "qcom,sdhci-msm-v5". - full compatible strings with SoC and version: - "qcom,apq8084-sdhci", "qcom,sdhci-msm-v4" - "qcom,msm8226-sdhci", "qcom,sdhci-msm-v4" - "qcom,msm8953-sdhci", "qcom,sdhci-msm-v4" - "qcom,msm8974-sdhci", "qcom,sdhci-msm-v4" - "qcom,msm8916-sdhci", "qcom,sdhci-msm-v4" - "qcom,msm8992-sdhci", "qcom,sdhci-msm-v4" - "qcom,msm8994-sdhci", "qcom,sdhci-msm-v4" - "qcom,msm8996-sdhci", "qcom,sdhci-msm-v4" - "qcom,qcs404-sdhci", "qcom,sdhci-msm-v5" - "qcom,sc7180-sdhci", "qcom,sdhci-msm-v5"; - "qcom,sc7280-sdhci", "qcom,sdhci-msm-v5"; - "qcom,sdm845-sdhci", "qcom,sdhci-msm-v5" - "qcom,sdx55-sdhci", "qcom,sdhci-msm-v5"; - "qcom,sm8250-sdhci", "qcom,sdhci-msm-v5" - NOTE that some old device tree files may be floating around that only - have the string "qcom,sdhci-msm-v4" without the SoC compatible string - but doing that should be considered a deprecated practice. - -- reg: Base address and length of the register in the following order: - - Host controller register map (required) - - SD Core register map (required for controllers earlier than msm-v5) - - CQE register map (Optional, CQE support is present on SDHC instance meant - for eMMC and version v4.2 and above) - - Inline Crypto Engine register map (optional) -- reg-names: When CQE register map is supplied, below reg-names are required - - "hc" for Host controller register map - - "core" for SD core register map - - "cqhci" for CQE register map - - "ice" for Inline Crypto Engine register map (optional) -- interrupts: Should contain an interrupt-specifiers for the interrupts: - - Host controller interrupt (required) -- pinctrl-names: Should contain only one value - "default". -- pinctrl-0: Should specify pin control groups used for this controller. -- clocks: A list of phandle + clock-specifier pairs for the clocks listed in clock-names. -- clock-names: Should contain the following: - "iface" - Main peripheral bus clock (PCLK/HCLK - AHB Bus clock) (required) - "core" - SDC MMC clock (MCLK) (required) - "bus" - SDCC bus voter clock (optional) - "xo" - TCXO clock (optional) - "cal" - reference clock for RCLK delay calibration (optional) - "sleep" - sleep clock for RCLK delay calibration (optional) - "ice" - clock for Inline Crypto Engine (optional) - -- qcom,ddr-config: Certain chipsets and platforms require particular settings - for the DDR_CONFIG register. Use this field to specify the register - value as per the Hardware Programming Guide. - -- qcom,dll-config: Chipset and Platform specific value. Use this field to - specify the DLL_CONFIG register value as per Hardware Programming Guide. - -Optional Properties: -* Following bus parameters are required for interconnect bandwidth scaling: -- interconnects: Pairs of phandles and interconnect provider specifier - to denote the edge source and destination ports of - the interconnect path. - -- interconnect-names: For sdhc, we have two main paths. - 1. Data path : sdhc to ddr - 2. Config path : cpu to sdhc - For Data interconnect path the name supposed to be - is "sdhc-ddr" and for config interconnect path it is - "cpu-sdhc". - Please refer to Documentation/devicetree/bindings/ - interconnect/ for more details. - -Example: - - sdhc_1: sdhci@f9824900 { - compatible = "qcom,msm8974-sdhci", "qcom,sdhci-msm-v4"; - reg = <0xf9824900 0x11c>, <0xf9824000 0x800>; - interrupts = <0 123 0>; - bus-width = <8>; - non-removable; - - vmmc-supply = <&pm8941_l20>; - vqmmc-supply = <&pm8941_s3>; - - pinctrl-names = "default"; - pinctrl-0 = <&sdc1_clk &sdc1_cmd &sdc1_data>; - - clocks = <&gcc GCC_SDCC1_APPS_CLK>, <&gcc GCC_SDCC1_AHB_CLK>; - clock-names = "core", "iface"; - interconnects = <&qnoc MASTER_SDCC_ID &qnoc SLAVE_DDR_ID>, - <&qnoc MASTER_CPU_ID &qnoc SLAVE_SDCC_ID>; - interconnect-names = "sdhc-ddr","cpu-sdhc"; - - qcom,dll-config = <0x000f642c>; - qcom,ddr-config = <0x80040868>; - }; - - sdhc_2: sdhci@f98a4900 { - compatible = "qcom,msm8974-sdhci", "qcom,sdhci-msm-v4"; - reg = <0xf98a4900 0x11c>, <0xf98a4000 0x800>; - interrupts = <0 125 0>; - bus-width = <4>; - cd-gpios = <&msmgpio 62 0x1>; - - vmmc-supply = <&pm8941_l21>; - vqmmc-supply = <&pm8941_l13>; - - pinctrl-names = "default"; - pinctrl-0 = <&sdc2_clk &sdc2_cmd &sdc2_data>; - - clocks = <&gcc GCC_SDCC2_APPS_CLK>, <&gcc GCC_SDCC2_AHB_CLK>; - clock-names = "core", "iface"; - - qcom,dll-config = <0x0007642c>; - qcom,ddr-config = <0x80040868>; - }; diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.yaml b/Documentation/devicetree/bindings/mmc/sdhci-msm.yaml new file mode 100644 index 000000000000..e4236334e748 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.yaml @@ -0,0 +1,194 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) + +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/mmc/sdhci-msm.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Qualcomm SDHCI controller (sdhci-msm) + +maintainers: + - Bhupesh Sharma + +description: + Secure Digital Host Controller Interface (SDHCI) present on + Qualcomm SOCs supports SD/MMC/SDIO devices. + +properties: + compatible: + oneOf: + - items: + - enum: + - qcom,apq8084-sdhci + - qcom,msm8226-sdhci + - qcom,msm8953-sdhci + - qcom,msm8974-sdhci + - qcom,msm8916-sdhci + - qcom,msm8992-sdhci + - qcom,msm8994-sdhci + - qcom,msm8996-sdhci + - qcom,qcs404-sdhci + - qcom,sc7180-sdhci + - qcom,sc7280-sdhci + - qcom,sdm630-sdhci + - qcom,sdm845-sdhci + - qcom,sdx55-sdhci + - qcom,sdx65-sdhci + - qcom,sm6125-sdhci + - qcom,sm6350-sdhci + - qcom,sm8150-sdhci + - qcom,sm8250-sdhci + - enum: + - qcom,sdhci-msm-v4 # for sdcc versions less than 5.0 + - qcom,sdhci-msm-v5 # for sdcc version 5.0 + - items: + - const: qcom,sdhci-msm-v4 # Deprecated (only for backward compatibility) + # for sdcc versions less than 5.0 + + reg: + minItems: 1 + items: + - description: Host controller register map + - description: SD Core register map + - description: CQE register map + - description: Inline Crypto Engine register map + + clocks: + minItems: 3 + items: + - description: Main peripheral bus clock, PCLK/HCLK - AHB Bus clock + - description: SDC MMC clock, MCLK + - description: TCXO clock + - description: clock for Inline Crypto Engine + - description: SDCC bus voter clock + - description: reference clock for RCLK delay calibration + - description: sleep clock for RCLK delay calibration + + clock-names: + minItems: 2 + items: + - const: iface + - const: core + - const: xo + - const: ice + - const: bus + - const: cal + - const: sleep + + interrupts: + maxItems: 2 + + interrupt-names: + items: + - const: hc_irq + - const: pwr_irq + + pinctrl-names: + minItems: 1 + items: + - const: default + - const: sleep + + pinctrl-0: + description: + Should specify pin control groups used for this controller. + + qcom,ddr-config: + $ref: /schemas/types.yaml#/definitions/uint32 + description: platform specific settings for DDR_CONFIG reg. + + qcom,dll-config: + $ref: /schemas/types.yaml#/definitions/uint32 + description: platform specific settings for DLL_CONFIG reg. + + iommus: + minItems: 1 + maxItems: 8 + description: | + phandle to apps_smmu node with sid mask. + + interconnects: + items: + - description: data path, sdhc to ddr + - description: config path, cpu to sdhc + + interconnect-names: + items: + - const: sdhc-ddr + - const: cpu-sdhc + + power-domains: + description: A phandle to sdhci power domain node + maxItems: 1 + +patternProperties: + '^opp-table(-[a-z0-9]+)?$': + if: + properties: + compatible: + const: operating-points-v2 + then: + patternProperties: + '^opp-?[0-9]+$': + required: + - required-opps + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + +additionalProperties: true + +examples: + - | + #include + #include + #include + #include + + sdhc_2: sdhci@8804000 { + compatible = "qcom,sm8250-sdhci", "qcom,sdhci-msm-v5"; + reg = <0 0x08804000 0 0x1000>; + + interrupts = , + ; + interrupt-names = "hc_irq", "pwr_irq"; + + clocks = <&gcc GCC_SDCC2_AHB_CLK>, + <&gcc GCC_SDCC2_APPS_CLK>, + <&rpmhcc RPMH_CXO_CLK>; + clock-names = "iface", "core", "xo"; + iommus = <&apps_smmu 0x4a0 0x0>; + qcom,dll-config = <0x0007642c>; + qcom,ddr-config = <0x80040868>; + power-domains = <&rpmhpd SM8250_CX>; + + operating-points-v2 = <&sdhc2_opp_table>; + + sdhc2_opp_table: opp-table { + compatible = "operating-points-v2"; + + opp-19200000 { + opp-hz = /bits/ 64 <19200000>; + required-opps = <&rpmhpd_opp_min_svs>; + }; + + opp-50000000 { + opp-hz = /bits/ 64 <50000000>; + required-opps = <&rpmhpd_opp_low_svs>; + }; + + opp-100000000 { + opp-hz = /bits/ 64 <100000000>; + required-opps = <&rpmhpd_opp_svs>; + }; + + opp-202000000 { + opp-hz = /bits/ 64 <202000000>; + required-opps = <&rpmhpd_opp_svs_l1>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml index f300ced4cdf3..71f8e726d641 100644 --- a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml +++ b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml @@ -17,6 +17,7 @@ properties: compatible: enum: - rockchip,rk3568-dwcmshc + - rockchip,rk3588-dwcmshc - snps,dwcmshc-sdhci reg: diff --git a/Documentation/devicetree/bindings/mtd/aspeed-smc.txt b/Documentation/devicetree/bindings/mtd/aspeed-smc.txt deleted file mode 100644 index 49f6528ef547..000000000000 --- a/Documentation/devicetree/bindings/mtd/aspeed-smc.txt +++ /dev/null @@ -1,51 +0,0 @@ -* Aspeed Firmware Memory controller -* Aspeed SPI Flash Memory Controller - -The Firmware Memory Controller in the Aspeed AST2500 SoC supports -three chip selects, two of which are always of SPI type and the third -can be SPI or NOR type flash. These bindings only describe SPI. - -The two SPI flash memory controllers in the AST2500 each support two -chip selects. - -Required properties: - - compatible : Should be one of - "aspeed,ast2400-fmc" for the AST2400 Firmware Memory Controller - "aspeed,ast2400-spi" for the AST2400 SPI Flash memory Controller - "aspeed,ast2500-fmc" for the AST2500 Firmware Memory Controller - "aspeed,ast2500-spi" for the AST2500 SPI flash memory controllers - - - reg : the first contains the control register location and length, - the second contains the memory window mapping address and length - - #address-cells : must be 1 corresponding to chip select child binding - - #size-cells : must be 0 corresponding to chip select child binding - -Optional properties: - - interrupts : Should contain the interrupt for the dma device if an - FMC - -The child nodes are the SPI flash modules which must have a compatible -property as specified in bindings/mtd/jedec,spi-nor.txt - -Optionally, the child node can contain properties for SPI mode (may be -ignored): - - spi-max-frequency - max frequency of spi bus - - -Example: -fmc: fmc@1e620000 { - compatible = "aspeed,ast2500-fmc"; - reg = < 0x1e620000 0x94 - 0x20000000 0x02000000 >; - #address-cells = <1>; - #size-cells = <0>; - interrupts = <19>; - flash@0 { - reg = < 0 >; - compatible = "jedec,spi-nor"; - /* spi-max-frequency = <>; */ - /* m25p,fast-read; */ - #address-cells = <1>; - #size-cells = <1>; - }; -}; diff --git a/Documentation/devicetree/bindings/mtd/elm.txt b/Documentation/devicetree/bindings/mtd/elm.txt deleted file mode 100644 index 59ddc61c1076..000000000000 --- a/Documentation/devicetree/bindings/mtd/elm.txt +++ /dev/null @@ -1,16 +0,0 @@ -Error location module - -Required properties: -- compatible: Must be "ti,am3352-elm" -- reg: physical base address and size of the registers map. -- interrupts: Interrupt number for the elm. - -Optional properties: -- ti,hwmods: Name of the hwmod associated to the elm - -Example: -elm: elm@0 { - compatible = "ti,am3352-elm"; - reg = <0x48080000 0x2000>; - interrupts = <4>; -}; diff --git a/Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml b/Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml index 4abfb4cfc157..7149784a36ac 100644 --- a/Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml +++ b/Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml @@ -50,10 +50,6 @@ properties: minItems: 1 maxItems: 2 - spi-max-frequency: true - spi-rx-bus-width: true - spi-tx-bus-width: true - m25p,fast-read: type: boolean description: @@ -74,8 +70,6 @@ properties: be used on such systems, to denote the absence of a reliable reset mechanism. - label: true - partitions: type: object @@ -99,8 +93,6 @@ examples: #size-cells = <0>; flash@0 { - #address-cells = <1>; - #size-cells = <1>; compatible = "spansion,m25p80", "jedec,spi-nor"; reg = <0>; spi-max-frequency = <40000000>; diff --git a/Documentation/devicetree/bindings/mtd/partitions/fixed-partitions.yaml b/Documentation/devicetree/bindings/mtd/partitions/fixed-partitions.yaml index ea4cace6a955..ad3ccd250802 100644 --- a/Documentation/devicetree/bindings/mtd/partitions/fixed-partitions.yaml +++ b/Documentation/devicetree/bindings/mtd/partitions/fixed-partitions.yaml @@ -19,7 +19,11 @@ maintainers: properties: compatible: - const: fixed-partitions + oneOf: + - const: fixed-partitions + - items: + - const: sercomm,sc-partitions + - const: fixed-partitions "#address-cells": true @@ -27,7 +31,24 @@ properties: patternProperties: "@[0-9a-f]+$": - $ref: "partition.yaml#" + allOf: + - $ref: "partition.yaml#" + - if: + properties: + compatible: + contains: + const: sercomm,sc-partitions + then: + properties: + sercomm,scpart-id: + description: Partition id in Sercomm partition map. Mtd + parser uses this id to find a record in the partition map + containing offset and size of the current partition. The + values from partition map overrides partition offset and + size defined in reg property of the dts. Frequently these + values are the same, but may differ if device has bad + eraseblocks on a flash. + $ref: /schemas/types.yaml#/definitions/uint32 required: - "#address-cells" @@ -52,6 +73,7 @@ examples: reg = <0x0100000 0x200000>; }; }; + - | partitions { compatible = "fixed-partitions"; @@ -64,6 +86,7 @@ examples: reg = <0x00000000 0x1 0x00000000>; }; }; + - | partitions { compatible = "fixed-partitions"; @@ -82,6 +105,7 @@ examples: reg = <0x2 0x00000000 0x1 0x00000000>; }; }; + - | partitions { compatible = "fixed-partitions"; @@ -119,3 +143,30 @@ examples: }; }; }; + + - | + partitions { + compatible = "sercomm,sc-partitions", "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + partition@0 { + label = "u-boot"; + reg = <0x0 0x100000>; + sercomm,scpart-id = <0>; + read-only; + }; + + partition@100000 { + label = "dynamic partition map"; + reg = <0x100000 0x100000>; + sercomm,scpart-id = <1>; + }; + + partition@200000 { + label = "Factory"; + reg = <0x200000 0x100000>; + sercomm,scpart-id = <2>; + read-only; + }; + }; diff --git a/Documentation/devicetree/bindings/mtd/renesas-nandc.yaml b/Documentation/devicetree/bindings/mtd/renesas-nandc.yaml index 2870d36361c4..7b18bc5cc8b3 100644 --- a/Documentation/devicetree/bindings/mtd/renesas-nandc.yaml +++ b/Documentation/devicetree/bindings/mtd/renesas-nandc.yaml @@ -36,11 +36,15 @@ properties: - const: hclk - const: eclk + power-domains: + maxItems: 1 + required: - compatible - reg - clocks - clock-names + - power-domains - interrupts unevaluatedProperties: false @@ -56,6 +60,7 @@ examples: interrupts = ; clocks = <&sysctrl R9A06G032_HCLK_NAND>, <&sysctrl R9A06G032_CLK_NAND>; clock-names = "hclk", "eclk"; + power-domains = <&sysctrl>; #address-cells = <1>; #size-cells = <0>; }; diff --git a/Documentation/devicetree/bindings/mtd/ti,elm.yaml b/Documentation/devicetree/bindings/mtd/ti,elm.yaml new file mode 100644 index 000000000000..87128c004596 --- /dev/null +++ b/Documentation/devicetree/bindings/mtd/ti,elm.yaml @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mtd/ti,elm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments Error Location Module (ELM). + +maintainers: + - Roger Quadros + +description: + ELM module is used together with GPMC and NAND Flash to detect + errors and the location of the error based on BCH algorithms + so they can be corrected if possible. + +properties: + compatible: + enum: + - ti,am3352-elm + - ti,am64-elm + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + description: Functional clock. + + clock-names: + items: + - const: fck + + power-domains: + maxItems: 1 + + ti,hwmods: + description: + Name of the HWMOD associated with ELM. This is for legacy + platforms only. + $ref: /schemas/types.yaml#/definitions/string + deprecated: true + +required: + - compatible + - reg + - interrupts + +allOf: + - if: + properties: + compatible: + contains: + const: ti,am64-elm + then: + required: + - clocks + - clock-names + - power-domains + +additionalProperties: false + +examples: + - | + elm: ecc@0 { + compatible = "ti,am3352-elm"; + reg = <0x0 0x2000>; + interrupts = <4>; + }; diff --git a/Documentation/devicetree/bindings/regulator/mt6315-regulator.yaml b/Documentation/devicetree/bindings/regulator/mt6315-regulator.yaml index 61dd5af80db6..5d2d989de893 100644 --- a/Documentation/devicetree/bindings/regulator/mt6315-regulator.yaml +++ b/Documentation/devicetree/bindings/regulator/mt6315-regulator.yaml @@ -31,7 +31,7 @@ properties: $ref: "regulator.yaml#" properties: - regulator-name: + regulator-compatible: pattern: "^vbuck[1-4]$" additionalProperties: false diff --git a/Documentation/devicetree/bindings/regulator/mt6358-regulator.txt b/Documentation/devicetree/bindings/regulator/mt6358-regulator.txt index 9a90a92f2d7e..7034cdca54e0 100644 --- a/Documentation/devicetree/bindings/regulator/mt6358-regulator.txt +++ b/Documentation/devicetree/bindings/regulator/mt6358-regulator.txt @@ -8,14 +8,14 @@ Documentation/devicetree/bindings/regulator/regulator.txt. The valid names for regulators are:: BUCK: - buck_vdram1, buck_vcore, buck_vpa, buck_vproc11, buck_vproc12, buck_vgpu, - buck_vs2, buck_vmodem, buck_vs1 + buck_vdram1, buck_vcore, buck_vcore_sshub, buck_vpa, buck_vproc11, + buck_vproc12, buck_vgpu, buck_vs2, buck_vmodem, buck_vs1 LDO: ldo_vdram2, ldo_vsim1, ldo_vibr, ldo_vrf12, ldo_vio18, ldo_vusb, ldo_vcamio, ldo_vcamd, ldo_vcn18, ldo_vfe28, ldo_vsram_proc11, ldo_vcn28, ldo_vsram_others, - ldo_vsram_gpu, ldo_vxo22, ldo_vefuse, ldo_vaux18, ldo_vmch, ldo_vbif28, - ldo_vsram_proc12, ldo_vcama1, ldo_vemc, ldo_vio28, ldo_va12, ldo_vrf18, - ldo_vcn33_bt, ldo_vcn33_wifi, ldo_vcama2, ldo_vmc, ldo_vldo28, ldo_vaud28, + ldo_vsram_others_sshub, ldo_vsram_gpu, ldo_vxo22, ldo_vefuse, ldo_vaux18, + ldo_vmch, ldo_vbif28, ldo_vsram_proc12, ldo_vcama1, ldo_vemc, ldo_vio28, ldo_va12, + ldo_vrf18, ldo_vcn33_bt, ldo_vcn33_wifi, ldo_vcama2, ldo_vmc, ldo_vldo28, ldo_vaud28, ldo_vsim2 Example: @@ -354,5 +354,17 @@ Example: regulator-max-microvolt = <3100000>; regulator-enable-ramp-delay = <540>; }; + + mt6358_vcore_sshub_reg: buck_vcore_sshub { + regulator-name = "vcore_sshub"; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <1293750>; + }; + + mt6358_vsram_others_sshub_reg: ldo_vsram_others_sshub { + regulator-name = "vsram_others_sshub"; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <1293750>; + }; }; }; diff --git a/Documentation/devicetree/bindings/regulator/nxp,pca9450-regulator.yaml b/Documentation/devicetree/bindings/regulator/nxp,pca9450-regulator.yaml index f70f2e758a00..b539781e39aa 100644 --- a/Documentation/devicetree/bindings/regulator/nxp,pca9450-regulator.yaml +++ b/Documentation/devicetree/bindings/regulator/nxp,pca9450-regulator.yaml @@ -92,6 +92,17 @@ properties: LDO5CTRL_L or LDO5CTRL_H register. Use this if the SD_VSEL signal is connected to a host GPIO. + nxp,i2c-lt-enable: + type: boolean + description: + Indicates that the I2C Level Translator is used. + + nxp,wdog_b-warm-reset: + type: boolean + description: + When WDOG_B signal is asserted a warm reset will be done instead of cold + reset. + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/regulator/qcom,rpmh-regulator.yaml b/Documentation/devicetree/bindings/regulator/qcom,rpmh-regulator.yaml index e28ee9e46788..9a36bee750af 100644 --- a/Documentation/devicetree/bindings/regulator/qcom,rpmh-regulator.yaml +++ b/Documentation/devicetree/bindings/regulator/qcom,rpmh-regulator.yaml @@ -7,7 +7,8 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Qualcomm Technologies, Inc. RPMh Regulators maintainers: - - David Collins + - Bjorn Andersson + - Krzysztof Kozlowski description: | rpmh-regulator devices support PMIC regulator management via the Voltage @@ -78,7 +79,7 @@ properties: RPMh resource name suffix used for the regulators found on this PMIC. $ref: /schemas/types.yaml#/definitions/string - enum: [a, b, c, d, e, f] + enum: [a, b, c, d, e, f, h, k] qcom,always-wait-for-ack: description: | @@ -94,35 +95,264 @@ properties: vdd-rgb-supply: description: Input supply phandle of rgb. - vin-lvs-1-2-supply: - description: Input supply phandle of one or more regulators. - - vdd-bob-supply: - description: BOB regulator parent supply phandle. - bob: type: object $ref: "regulator.yaml#" description: BOB regulator node. patternProperties: - "^vdd-s([0-9]+)-supply$": - description: Input supply phandle(s) of one or more regulators. - - "^vdd-(l[0-9]+[-]){1,5}supply$": - description: Input supply phandle(s) of one or more regulators. - "^(smps|ldo|lvs)[0-9]+$": type: object $ref: "regulator.yaml#" description: smps/ldo regulator nodes(s). -additionalProperties: false - required: - compatible - qcom,pmic-id +allOf: + - if: + properties: + compatible: + enum: + - qcom,pm6150-rpmh-regulators + then: + properties: + vdd-l2-l3-supply: true + vdd-l4-l7-l8-supply: true + vdd-l5-l16-l17-l18-l19-supply: true + vdd-l10-l14-l15-supply: true + vdd-l11-l12-l13-supply: true + patternProperties: + "^vdd-l[169]-supply$": true + "^vdd-s[1-5]-supply$": true + + - if: + properties: + compatible: + enum: + - qcom,pm6150l-rpmh-regulators + then: + properties: + vdd-bob-supply: + description: BOB regulator parent supply phandle. + vdd-l1-l8-supply: true + vdd-l2-l3-supply: true + vdd-l4-l5-l6-supply: true + vdd-l7-l11-supply: true + vdd-l9-l10-supply: true + patternProperties: + "^vdd-s[1-8]-supply$": true + + - if: + properties: + compatible: + enum: + - qcom,pm7325-rpmh-regulators + then: + properties: + vdd-l1-l4-l12-l15-supply: true + vdd-l2-l7-supply: true + vdd-l6-l9-l10-supply: true + vdd-l11-l17-l18-l19-supply: true + vdd-l13-supply: true + vdd-l14-l16-supply: true + patternProperties: + "^vdd-l[358]-supply$": true + "^vdd-s[1-8]-supply$": true + + - if: + properties: + compatible: + enum: + - qcom,pm8005-rpmh-regulators + then: + patternProperties: + "^vdd-s[1-4]-supply$": true + + - if: + properties: + compatible: + enum: + - qcom,pm8009-rpmh-regulators + - qcom,pm8009-1-rpmh-regulators + then: + properties: + vdd-l5-l6-supply: true + patternProperties: + "^vdd-l[1-47]-supply$": true + "^vdd-s[1-2]-supply$": true + + - if: + properties: + compatible: + enum: + - qcom,pm8150-rpmh-regulators + - qcom,pmm8155au-rpmh-regulators + then: + properties: + vdd-l1-l8-l11-supply: true + vdd-l2-l10-supply: true + vdd-l3-l4-l5-l18-supply: true + vdd-l6-l9-supply: true + vdd-l7-l12-l14-l15-supply: true + vdd-l13-l16-l17-supply: true + patternProperties: + "^vdd-s([1-9]|10)-supply$": true + + - if: + properties: + compatible: + enum: + - qcom,pm8150l-rpmh-regulators + then: + properties: + vdd-bob-supply: + description: BOB regulator parent supply phandle. + vdd-l1-l8-supply: true + vdd-l2-l3-supply: true + vdd-l4-l5-l6-supply: true + vdd-l7-l11-supply: true + vdd-l9-l10-supply: true + patternProperties: + "^vdd-s[1-8]-supply$": true + + - if: + properties: + compatible: + enum: + - qcom,pm8350-rpmh-regulators + then: + properties: + vdd-l1-l4-supply: true + vdd-l2-l7-supply: true + vdd-l3-l5-supply: true + vdd-l6-l9-l10-supply: true + vdd-l8-supply: true + patternProperties: + "^vdd-s([1-9]|1[0-2])-supply$": true + + - if: + properties: + compatible: + enum: + - qcom,pm8350c-rpmh-regulators + then: + properties: + vdd-bob-supply: + description: BOB regulator parent supply phandle. + vdd-l1-l12-supply: true + vdd-l2-l8-supply: true + vdd-l3-l4-l5-l7-l13-supply: true + vdd-l6-l9-l11-supply: true + vdd-l10-supply: true + patternProperties: + "^vdd-s([1-9]|10)-supply$": true + + - if: + properties: + compatible: + enum: + - qcom,pm8450-rpmh-regulators + then: + patternProperties: + "^vdd-l[1-4]-supply$": true + "^vdd-s[1-6]-supply$": true + + - if: + properties: + compatible: + enum: + - qcom,pm8998-rpmh-regulators + then: + properties: + vdd-l1-l27-supply: true + vdd-l2-l8-l17-supply: true + vdd-l3-l11-supply: true + vdd-l4-l5-supply: true + vdd-l6-supply: true + vdd-l7-l12-l14-l15-supply: true + vdd-l9-supply: true + vdd-l10-l23-l25-supply: true + vdd-l13-l19-l21-supply: true + vdd-l16-l28-supply: true + vdd-l18-l22-supply: true + vdd-l20-l24-supply: true + vdd-l26-supply: true + vin-lvs-1-2-supply: true + patternProperties: + "^vdd-s([1-9]|1[0-3])-supply$": true + + - if: + properties: + compatible: + enum: + - qcom,pmg1110-rpmh-regulators + then: + properties: + vdd-s1-supply: true + + - if: + properties: + compatible: + enum: + - qcom,pmi8998-rpmh-regulators + then: + properties: + vdd-bob-supply: + description: BOB regulator parent supply phandle. + + - if: + properties: + compatible: + enum: + - qcom,pmr735a-rpmh-regulators + then: + properties: + vdd-l1-l2-supply: true + vdd-l3-supply: true + vdd-l4-supply: true + vdd-l5-l6-supply: true + vdd-l7-bob-supply: true + patternProperties: + "^vdd-s[1-3]-supply$": true + + - if: + properties: + compatible: + enum: + - qcom,pmx55-rpmh-regulators + then: + properties: + vdd-l1-l2-supply: true + vdd-l3-l9-supply: true + vdd-l4-l12-supply: true + vdd-l5-l6-supply: true + vdd-l7-l8-supply: true + vdd-l10-l11-l13-supply: true + patternProperties: + "^vdd-l1[4-6]-supply$": true + "^vdd-s[1-7]-supply$": true + + - if: + properties: + compatible: + enum: + - qcom,pmx65-rpmh-regulators + then: + properties: + vdd-l2-l18-supply: true + vdd-l5-l6-l16-supply: true + vdd-l8-l9-supply: true + vdd-l11-l13-supply: true + patternProperties: + "^vdd-l[1347]-supply$": true + "^vdd-l1[0245789]-supply$": true + "^vdd-l2[01]-supply$": true + "^vdd-s[1-8]-supply$": true + +unevaluatedProperties: false + examples: - | #include diff --git a/Documentation/devicetree/bindings/regulator/richtek,rt4801-regulator.yaml b/Documentation/devicetree/bindings/regulator/richtek,rt4801-regulator.yaml index 235e593b3b2c..091150c4e579 100644 --- a/Documentation/devicetree/bindings/regulator/richtek,rt4801-regulator.yaml +++ b/Documentation/devicetree/bindings/regulator/richtek,rt4801-regulator.yaml @@ -17,9 +17,6 @@ description: | Datasheet is available at https://www.richtek.com/assets/product_file/RT4801H/DS4801H-00.pdf -#The valid names for RT4801 regulator nodes are: -#DSVP, DSVN - properties: compatible: enum: @@ -33,10 +30,13 @@ properties: The first one is ENP to enable DSVP, and second one is ENM to enable DSVN. Number of GPIO in the array list could be 1 or 2. If only one gpio is specified, only one gpio used to control ENP/ENM. - Else both are spefied, DSVP/DSVN could be controlled individually. - Othersie, this property not specified. treat both as always-on regulator. + Else if both are specified, DSVP/DSVN could be controlled individually. + If this property not specified, treat both as always-on regulators. + + Property is deprecated. Use enable-gpios in each regulator. minItems: 1 maxItems: 2 + deprecated: true patternProperties: "^DSV(P|N)$": @@ -45,6 +45,14 @@ patternProperties: description: Properties for single display bias regulator. + properties: + enable-gpios: + description: + GPIO to use to enable DSVP/DSVN regulator. One GPIO can be configured + for controlling both regulators. If this property not specified for + any regulator, treat both as always-on regulators. + maxItems: 1 + required: - compatible - reg @@ -60,19 +68,20 @@ examples: rt4801@73 { compatible = "richtek,rt4801"; reg = <0x73>; - enable-gpios = <&gpio26 2 0>, <&gpio26 3 0>; dsvp: DSVP { regulator-name = "rt4801,dsvp"; regulator-min-microvolt = <4000000>; regulator-max-microvolt = <6000000>; regulator-boot-on; + enable-gpios = <&gpio26 2 0>; }; dsvn: DSVN { regulator-name = "rt4801,dsvn"; regulator-min-microvolt = <4000000>; regulator-max-microvolt = <6000000>; regulator-boot-on; + enable-gpios = <&gpio26 3 0>; }; }; diff --git a/Documentation/devicetree/bindings/regulator/richtek,rt5759-regulator.yaml b/Documentation/devicetree/bindings/regulator/richtek,rt5759-regulator.yaml new file mode 100644 index 000000000000..0a4c9576a432 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/richtek,rt5759-regulator.yaml @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/richtek,rt5759-regulator.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Richtek RT5759 High Performance DCDC Converter + +maintainers: + - ChiYuan Huang + +description: | + The RT5759 is a high-performance, synchronous step-down DC-DC converter that + can deliver up to 9A output current from 3V to 6.5V input supply, The output + voltage can be programmable with I2C controlled 7-Bit VID. + + Datasheet is available at + https://www.richtek.com/assets/product_file/RT5759/DS5759-00.pdf + +properties: + compatible: + enum: + - richtek,rt5759 + - richtek,rt5759a + + reg: + maxItems: 1 + + regulator-allowed-modes: + description: | + buck allowed operating mode + 0: auto mode (PSKIP: pulse skipping) + 1: force pwm mode + items: + enum: [0, 1] + + richtek,watchdog-enable: + description: enable the external watchdog reset pin listening + type: boolean + +allOf: + - $ref: regulator.yaml# + + - if: + properties: + compatible: + contains: + const: richtek,rt5759 + then: + properties: + richtek,watchdog-enable: false + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + # example 1 for RT5759 + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + regulator@62 { + compatible = "richtek,rt5759"; + reg = <0x62>; + regulator-name = "rt5759-buck"; + regulator-min-microvolt = <600000>; + regulator-max-microvolt = <1500000>; + regulator-boot-on; + }; + }; + # example 2 for RT5759A + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + regulator@62 { + compatible = "richtek,rt5759a"; + reg = <0x62>; + regulator-name = "rt5759a-buck"; + regulator-min-microvolt = <600000>; + regulator-max-microvolt = <1725000>; + regulator-boot-on; + richtek,watchdog-enable; + }; + }; diff --git a/Documentation/devicetree/bindings/regulator/siliconmitus,sm5703-regulator.yaml b/Documentation/devicetree/bindings/regulator/siliconmitus,sm5703-regulator.yaml new file mode 100644 index 000000000000..9d84117530ca --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/siliconmitus,sm5703-regulator.yaml @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/siliconmitus,sm5703-regulator.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Silicon Mitus SM5703 multi function device regulators + +maintainers: + - Markuss Broks + +description: | + SM5703 regulators node should be a sub node of the SM5703 MFD node. See SM5703 MFD + bindings at Documentation/devicetree/bindings/mfd/siliconmitus,sm5703.yaml + Regulator nodes should be named as USBLDO_, BUCK, VBUS, LDO_. + The definition for each of these nodes is defined using the standard + binding for regulators at Documentation/devicetree/bindings/regulator/regulator.txt. + +properties: + buck: + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for the BUCK regulator. + + vbus: + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for the VBUS regulator. + +patternProperties: + "^ldo[1-3]$": + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for single LDO regulator. + + "^usbldo[1-2]$": + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for a single USBLDO regulator. + +additionalProperties: false diff --git a/Documentation/devicetree/bindings/regulator/socionext,uniphier-regulator.yaml b/Documentation/devicetree/bindings/regulator/socionext,uniphier-regulator.yaml index 1218f21ba320..75087c6e001c 100644 --- a/Documentation/devicetree/bindings/regulator/socionext,uniphier-regulator.yaml +++ b/Documentation/devicetree/bindings/regulator/socionext,uniphier-regulator.yaml @@ -14,9 +14,6 @@ description: | maintainers: - Kunihiko Hayashi -allOf: - - $ref: "regulator.yaml#" - # USB3 Controller properties: @@ -36,27 +33,51 @@ properties: minItems: 1 maxItems: 2 - clock-names: - oneOf: - - items: # for Pro4, Pro5 - - const: gio - - const: link - - items: # for others - - const: link + clock-names: true resets: minItems: 1 maxItems: 2 - reset-names: - oneOf: - - items: # for Pro4, Pro5 - - const: gio - - const: link - - items: - - const: link + reset-names: true -additionalProperties: false +allOf: + - $ref: "regulator.yaml#" + - if: + properties: + compatible: + contains: + enum: + - socionext,uniphier-pro4-usb3-regulator + - socionext,uniphier-pro5-usb3-regulator + then: + properties: + clocks: + minItems: 2 + maxItems: 2 + clock-names: + items: + - const: gio + - const: link + resets: + minItems: 2 + maxItems: 2 + reset-names: + items: + - const: gio + - const: link + else: + properties: + clocks: + maxItems: 1 + clock-names: + const: link + resets: + maxItems: 1 + reset-names: + const: link + +unevaluatedProperties: false required: - compatible diff --git a/Documentation/devicetree/bindings/reserved-memory/phram.yaml b/Documentation/devicetree/bindings/reserved-memory/phram.yaml new file mode 100644 index 000000000000..6c4db28015f1 --- /dev/null +++ b/Documentation/devicetree/bindings/reserved-memory/phram.yaml @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/reserved-memory/phram.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MTD/block device in RAM + +description: | + Specifies that the reserved memory region can be used as an MTD or block + device. + + The "phram" node is named after the "MTD in PHysical RAM" driver which + provides an implementation of this functionality in Linux. + +maintainers: + - Vincent Whitchurch + +allOf: + - $ref: "reserved-memory.yaml" + - $ref: "/schemas/mtd/mtd.yaml" + +properties: + compatible: + const: phram + + reg: + description: region of memory that can be used as an MTD/block device + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; + + phram: flash@12340000 { + compatible = "phram"; + label = "rootfs"; + reg = <0x12340000 0x00800000>; + }; + }; diff --git a/Documentation/devicetree/bindings/spi/aspeed,ast2600-fmc.yaml b/Documentation/devicetree/bindings/spi/aspeed,ast2600-fmc.yaml new file mode 100644 index 000000000000..fa8f4ac20985 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/aspeed,ast2600-fmc.yaml @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/aspeed,ast2600-fmc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Aspeed SMC controllers bindings + +maintainers: + - Chin-Ting Kuo + - Cédric Le Goater + +description: | + This binding describes the Aspeed Static Memory Controllers (FMC and + SPI) of the AST2400, AST2500 and AST2600 SOCs. + +allOf: + - $ref: "spi-controller.yaml#" + +properties: + compatible: + enum: + - aspeed,ast2600-fmc + - aspeed,ast2600-spi + - aspeed,ast2500-fmc + - aspeed,ast2500-spi + - aspeed,ast2400-fmc + - aspeed,ast2400-spi + + reg: + items: + - description: registers + - description: memory mapping + + clocks: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - clocks + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + spi@1e620000 { + reg = <0x1e620000 0xc4>, <0x20000000 0x10000000>; + #address-cells = <1>; + #size-cells = <0>; + compatible = "aspeed,ast2600-fmc"; + clocks = <&syscon ASPEED_CLK_AHB>; + interrupts = ; + + flash@0 { + reg = < 0 >; + compatible = "jedec,spi-nor"; + spi-max-frequency = <50000000>; + spi-rx-bus-width = <2>; + }; + + flash@1 { + reg = < 1 >; + compatible = "jedec,spi-nor"; + spi-max-frequency = <50000000>; + spi-rx-bus-width = <2>; + }; + + flash@2 { + reg = < 2 >; + compatible = "jedec,spi-nor"; + spi-max-frequency = <50000000>; + spi-rx-bus-width = <2>; + }; + }; diff --git a/Documentation/devicetree/bindings/spi/ingenic,spi.yaml b/Documentation/devicetree/bindings/spi/ingenic,spi.yaml index 5b1c7a2a6a31..360f76c226d9 100644 --- a/Documentation/devicetree/bindings/spi/ingenic,spi.yaml +++ b/Documentation/devicetree/bindings/spi/ingenic,spi.yaml @@ -18,7 +18,10 @@ properties: oneOf: - enum: - ingenic,jz4750-spi + - ingenic,jz4775-spi - ingenic,jz4780-spi + - ingenic,x1000-spi + - ingenic,x2000-spi - items: - enum: - ingenic,jz4760-spi diff --git a/Documentation/devicetree/bindings/spi/mediatek,spi-mt65xx.yaml b/Documentation/devicetree/bindings/spi/mediatek,spi-mt65xx.yaml index 818130b11bb9..94ef0552bd42 100644 --- a/Documentation/devicetree/bindings/spi/mediatek,spi-mt65xx.yaml +++ b/Documentation/devicetree/bindings/spi/mediatek,spi-mt65xx.yaml @@ -53,16 +53,20 @@ properties: maxItems: 1 clocks: + minItems: 3 items: - description: clock used for the parent clock - description: clock used for the muxes clock - description: clock used for the clock gate + - description: clock used for the AHB bus, this clock is optional clock-names: + minItems: 3 items: - const: parent-clk - const: sel-clk - const: spi-clk + - const: hclk mediatek,pad-select: $ref: /schemas/types.yaml#/definitions/uint32-array diff --git a/Documentation/devicetree/bindings/spi/mediatek,spi-mtk-snfi.yaml b/Documentation/devicetree/bindings/spi/mediatek,spi-mtk-snfi.yaml new file mode 100644 index 000000000000..6e6e02c91780 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/mediatek,spi-mtk-snfi.yaml @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/mediatek,spi-mtk-snfi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SPI-NAND flash controller for MediaTek ARM SoCs + +maintainers: + - Chuanhong Guo + +description: | + The Mediatek SPI-NAND flash controller is an extended version of + the Mediatek NAND flash controller. It can perform standard SPI + instructions with one continuous write and one read for up-to 0xa0 + bytes. It also supports typical SPI-NAND page cache operations + in single, dual or quad IO mode with pipelined ECC encoding/decoding + using the accompanying ECC engine. There should be only one spi + slave device following generic spi bindings. + +allOf: + - $ref: /schemas/spi/spi-controller.yaml# + +properties: + compatible: + enum: + - mediatek,mt7622-snand + - mediatek,mt7629-snand + + reg: + items: + - description: core registers + + interrupts: + items: + - description: NFI interrupt + + clocks: + items: + - description: clock used for the controller + - description: clock used for the SPI bus + + clock-names: + items: + - const: nfi_clk + - const: pad_clk + + nand-ecc-engine: + description: device-tree node of the accompanying ECC engine. + $ref: /schemas/types.yaml#/definitions/phandle + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - nand-ecc-engine + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + soc { + #address-cells = <2>; + #size-cells = <2>; + snfi: spi@1100d000 { + compatible = "mediatek,mt7622-snand"; + reg = <0 0x1100d000 0 0x1000>; + interrupts = ; + clocks = <&pericfg CLK_PERI_NFI_PD>, <&pericfg CLK_PERI_SNFI_PD>; + clock-names = "nfi_clk", "pad_clk"; + nand-ecc-engine = <&bch>; + #address-cells = <1>; + #size-cells = <0>; + + flash@0 { + compatible = "spi-nand"; + reg = <0>; + spi-tx-bus-width = <4>; + spi-rx-bus-width = <4>; + nand-ecc-engine = <&snfi>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/spi/qcom,spi-qcom-qspi.yaml b/Documentation/devicetree/bindings/spi/qcom,spi-qcom-qspi.yaml index 5a60fba14bba..44d08aa3fd85 100644 --- a/Documentation/devicetree/bindings/spi/qcom,spi-qcom-qspi.yaml +++ b/Documentation/devicetree/bindings/spi/qcom,spi-qcom-qspi.yaml @@ -49,6 +49,7 @@ properties: maxItems: 2 interconnect-names: + minItems: 1 items: - const: qspi-config - const: qspi-memory diff --git a/Documentation/devicetree/bindings/spi/renesas,rspi.yaml b/Documentation/devicetree/bindings/spi/renesas,rspi.yaml index 2c3c6bd6ec45..f45d3b75d6de 100644 --- a/Documentation/devicetree/bindings/spi/renesas,rspi.yaml +++ b/Documentation/devicetree/bindings/spi/renesas,rspi.yaml @@ -21,6 +21,7 @@ properties: - enum: - renesas,rspi-r7s72100 # RZ/A1H - renesas,rspi-r7s9210 # RZ/A2 + - renesas,r9a07g043-rspi # RZ/G2UL - renesas,r9a07g044-rspi # RZ/G2{L,LC} - renesas,r9a07g054-rspi # RZ/V2L - const: renesas,rspi-rz @@ -124,6 +125,7 @@ allOf: contains: enum: - renesas,qspi + - renesas,r9a07g043-rspi - renesas,r9a07g044-rspi - renesas,r9a07g054-rspi then: diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 550a2e5c9e05..c11520347a9d 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -143,6 +143,10 @@ properties: - infineon,xdpe12254 # Infineon Multi-phase Digital VR Controller xdpe12284 - infineon,xdpe12284 + # Infineon Multi-phase Digital VR Controller xdpe15284 + - infineon,xdpe15284 + # Infineon Multi-phase Digital VR Controller xdpe152c4 + - infineon,xdpe152c4 # Injoinic IP5108 2.0A Power Bank IC with I2C - injoinic,ip5108 # Injoinic IP5109 2.1A Power Bank IC with I2C diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 01430973ecec..65ff22364fb3 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1082,6 +1082,8 @@ patternProperties: description: Sensirion AG "^sensortek,.*": description: Sensortek Technology Corporation + "^sercomm,.*": + description: Sercomm (Suzhou) Corporation "^sff,.*": description: Small Form Factor Committee "^sgd,.*": diff --git a/Documentation/filesystems/fsverity.rst b/Documentation/filesystems/fsverity.rst index 8cc536d08f51..b7d42fd65e9d 100644 --- a/Documentation/filesystems/fsverity.rst +++ b/Documentation/filesystems/fsverity.rst @@ -70,12 +70,23 @@ must live on a read-write filesystem because they are independently updated and potentially user-installed, so dm-verity cannot be used. The base fs-verity feature is a hashing mechanism only; actually -authenticating the files is up to userspace. However, to meet some -users' needs, fs-verity optionally supports a simple signature -verification mechanism where users can configure the kernel to require -that all fs-verity files be signed by a key loaded into a keyring; see -`Built-in signature verification`_. Support for fs-verity file hashes -in IMA (Integrity Measurement Architecture) policies is also planned. +authenticating the files may be done by: + +* Userspace-only + +* Builtin signature verification + userspace policy + + fs-verity optionally supports a simple signature verification + mechanism where users can configure the kernel to require that + all fs-verity files be signed by a key loaded into a keyring; + see `Built-in signature verification`_. + +* Integrity Measurement Architecture (IMA) + + IMA supports including fs-verity file digests and signatures in the + IMA measurement list and verifying fs-verity based file signatures + stored as security.ima xattrs, based on policy. + User API ======== @@ -653,12 +664,12 @@ weren't already directly answered in other parts of this document. hashed and what to do with those hashes, such as log them, authenticate them, or add them to a measurement list. - IMA is planned to support the fs-verity hashing mechanism as an - alternative to doing full file hashes, for people who want the - performance and security benefits of the Merkle tree based hash. - But it doesn't make sense to force all uses of fs-verity to be - through IMA. As a standalone filesystem feature, fs-verity - already meets many users' needs, and it's testable like other + IMA supports the fs-verity hashing mechanism as an alternative + to full file hashes, for those who want the performance and + security benefits of the Merkle tree based hash. However, it + doesn't make sense to force all uses of fs-verity to be through + IMA. fs-verity already meets many users' needs even as a + standalone filesystem feature, and it's testable like other filesystem features e.g. with xfstests. :Q: Isn't fs-verity useless because the attacker can just modify the diff --git a/Documentation/firmware-guide/acpi/enumeration.rst b/Documentation/firmware-guide/acpi/enumeration.rst index 47fb4d6d4557..6b62425ef9cd 100644 --- a/Documentation/firmware-guide/acpi/enumeration.rst +++ b/Documentation/firmware-guide/acpi/enumeration.rst @@ -167,8 +167,7 @@ The table below shows an example of its usage:: Name (_DSD, Package () { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { - Package () {"interrupt-names", - Package (2) {"default", "alert"}}, + Package () { "interrupt-names", Package () { "default", "alert" } }, } ... }) diff --git a/Documentation/hwmon/aquacomputer_d5next.rst b/Documentation/hwmon/aquacomputer_d5next.rst index 3373e27b707d..717e28226cde 100644 --- a/Documentation/hwmon/aquacomputer_d5next.rst +++ b/Documentation/hwmon/aquacomputer_d5next.rst @@ -6,7 +6,9 @@ Kernel driver aquacomputer-d5next Supported devices: * Aquacomputer D5 Next watercooling pump +* Aquacomputer Farbwerk RGB controller * Aquacomputer Farbwerk 360 RGB controller +* Aquacomputer Octo fan controller Author: Aleksa Savic @@ -28,7 +30,10 @@ seems to require sending it a complete configuration. That includes addressable RGB LEDs, for which there is no standard sysfs interface. Thus, that task is better suited for userspace tools. -The Farbwerk 360 exposes four temperature sensors. Depending on the device, +The Octo exposes four temperature sensors and eight PWM controllable fans, along +with their speed (in RPM), power, voltage and current. + +The Farbwerk and Farbwerk 360 expose four temperature sensors. Depending on the device, not all sysfs and debugfs entries will be available. Usage notes diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index e7e8f1640f45..78ca69eda877 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -4,17 +4,20 @@ Kernel driver asus_ec_sensors ================================= Supported boards: - * PRIME X570-PRO, - * Pro WS X570-ACE, - * ROG CROSSHAIR VIII DARK HERO, + * PRIME X470-PRO + * PRIME X570-PRO + * Pro WS X570-ACE + * ProArt X570-CREATOR WIFI + * ROG CROSSHAIR VIII DARK HERO * ROG CROSSHAIR VIII HERO (WI-FI) - * ROG CROSSHAIR VIII FORMULA, - * ROG CROSSHAIR VIII HERO, - * ROG CROSSHAIR VIII IMPACT, - * ROG STRIX B550-E GAMING, - * ROG STRIX B550-I GAMING, - * ROG STRIX X570-E GAMING, - * ROG STRIX X570-F GAMING, + * ROG CROSSHAIR VIII FORMULA + * ROG CROSSHAIR VIII HERO + * ROG CROSSHAIR VIII IMPACT + * ROG STRIX B550-E GAMING + * ROG STRIX B550-I GAMING + * ROG STRIX X570-E GAMING + * ROG STRIX X570-E GAMING WIFI II + * ROG STRIX X570-F GAMING * ROG STRIX X570-I GAMING Authors: @@ -52,3 +55,5 @@ Module Parameters the path is mostly identical for them). If ASUS changes this path in a future BIOS update, this parameter can be used to override the stored in the driver value until it gets updated. + A special string ":GLOBAL_LOCK" can be passed to use the ACPI + global lock instead of a dedicated mutex. diff --git a/Documentation/hwmon/dell-smm-hwmon.rst b/Documentation/hwmon/dell-smm-hwmon.rst index d3323a96665d..e5d85e40972c 100644 --- a/Documentation/hwmon/dell-smm-hwmon.rst +++ b/Documentation/hwmon/dell-smm-hwmon.rst @@ -86,6 +86,13 @@ probe the BIOS on your machine and discover the appropriate codes. Again, when you find new codes, we'd be happy to have your patches! +``thermal`` interface +--------------------------- + +The driver also exports the fans as thermal cooling devices with +``type`` set to ``dell-smm-fan[1-3]``. This allows for easy fan control +using one of the thermal governors. + Module parameters ----------------- @@ -324,6 +331,8 @@ Reading of fan types causes erratic fan behaviour. Studio XPS 8000 Inspiron 580 + Inspiron 3505 + Fan-related SMM calls take too long (about 500ms). Inspiron 7720 Vostro 3360 diff --git a/Documentation/hwmon/hwmon-kernel-api.rst b/Documentation/hwmon/hwmon-kernel-api.rst index c41eb6108103..f3276b3a381a 100644 --- a/Documentation/hwmon/hwmon-kernel-api.rst +++ b/Documentation/hwmon/hwmon-kernel-api.rst @@ -50,6 +50,10 @@ register/unregister functions:: void devm_hwmon_device_unregister(struct device *dev); + char *hwmon_sanitize_name(const char *name); + + char *devm_hwmon_sanitize_name(struct device *dev, const char *name); + hwmon_device_register_with_groups registers a hardware monitoring device. The first parameter of this function is a pointer to the parent device. The name parameter is a pointer to the hwmon device name. The registration @@ -72,7 +76,7 @@ hwmon_device_register_with_info is the most comprehensive and preferred means to register a hardware monitoring device. It creates the standard sysfs attributes in the hardware monitoring core, letting the driver focus on reading from and writing to the chip instead of having to bother with sysfs attributes. -The parent device parameter cannot be NULL with non-NULL chip info. Its +The parent device parameter as well as the chip parameter must not be NULL. Its parameters are described in more detail below. devm_hwmon_device_register_with_info is similar to @@ -95,6 +99,18 @@ All supported hwmon device registration functions only accept valid device names. Device names including invalid characters (whitespace, '*', or '-') will be rejected. The 'name' parameter is mandatory. +If the driver doesn't use a static device name (for example it uses +dev_name()), and therefore cannot make sure the name only contains valid +characters, hwmon_sanitize_name can be used. This convenience function +will duplicate the string and replace any invalid characters with an +underscore. It will allocate memory for the new string and it is the +responsibility of the caller to release the memory when the device is +removed. + +devm_hwmon_sanitize_name is the resource managed version of +hwmon_sanitize_name; the memory will be freed automatically on device +removal. + Using devm_hwmon_device_register_with_info() -------------------------------------------- diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 863b76289159..a72c16872ec2 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -90,6 +90,7 @@ Hardware Monitoring Kernel Drivers jc42 k10temp k8temp + lan966x lineage-pem lm25066 lm63 @@ -223,6 +224,7 @@ Hardware Monitoring Kernel Drivers wm8350 xgene-hwmon xdpe12284 + xdpe152c4 zl6100 .. only:: subproject and html diff --git a/Documentation/hwmon/lan966x.rst b/Documentation/hwmon/lan966x.rst new file mode 100644 index 000000000000..1d1724afa5d2 --- /dev/null +++ b/Documentation/hwmon/lan966x.rst @@ -0,0 +1,40 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver lan966x-hwmon +=========================== + +Supported chips: + + * Microchip LAN9668 (sensor in SoC) + + Prefix: 'lan9668-hwmon' + + Datasheet: https://microchip-ung.github.io/lan9668_reginfo + +Authors: + + Michael Walle + +Description +----------- + +This driver implements support for the Microchip LAN9668 on-chip +temperature sensor as well as its fan controller. It provides one +temperature sensor and one fan controller. The temperature range +of the sensor is specified from -40 to +125 degrees Celsius and +its accuracy is +/- 5 degrees Celsius. The fan controller has a +tacho input and a PWM output with a customizable PWM output +frequency ranging from ~20Hz to ~650kHz. + +No alarms are supported by the SoC. + +The driver exports temperature values, fan tacho input and PWM +settings via the following sysfs files: + +**temp1_input** + +**fan1_input** + +**pwm1** + +**pwm1_freq** diff --git a/Documentation/hwmon/max16601.rst b/Documentation/hwmon/max16601.rst index 92c0a7d7808c..6a4eef8efbaf 100644 --- a/Documentation/hwmon/max16601.rst +++ b/Documentation/hwmon/max16601.rst @@ -21,6 +21,14 @@ Supported chips: Datasheet: Not published + * Maxim MAX16602 + + Prefix: 'max16602' + + Addresses scanned: - + + Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX16602.pdf + Author: Guenter Roeck diff --git a/Documentation/hwmon/xdpe152c4.rst b/Documentation/hwmon/xdpe152c4.rst new file mode 100644 index 000000000000..ab92c32d4d69 --- /dev/null +++ b/Documentation/hwmon/xdpe152c4.rst @@ -0,0 +1,118 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver xdpe152 +===================== + +Supported chips: + + * Infineon XDPE152C4 + + Prefix: 'xdpe152c4' + + * Infineon XDPE15284 + + Prefix: 'xdpe15284' + +Authors: + + Greg Schwendimann + +Description +----------- + +This driver implements support for Infineon Digital Multi-phase Controller +XDPE152C4 and XDPE15284 dual loop voltage regulators. +The devices are compliant with: + +- Intel VR13, VR13HC and VR14 rev 1.86 + converter specification. +- Intel SVID rev 1.93. protocol. +- PMBus rev 1.3.1 interface. + +Devices support linear format for reading input and output voltage, input +and output current, input and output power and temperature. + +Devices support two pages for telemetry. + +The driver provides for current: input, maximum and critical thresholds +and maximum and critical alarms. Low Critical thresholds and Low critical alarm are +supported only for current output. +The driver exports the following attributes for via the sysfs files, where +indexes 1, 2 are for "iin" and 3, 4 for "iout": + +**curr[1-4]_crit** + +**curr[1-4]_crit_alarm** + +**curr[1-4]_input** + +**curr[1-4]_label** + +**curr[1-4]_max** + +**curr[1-4]_max_alarm** + +**curr[3-4]_lcrit** + +**curr[3-4]_lcrit_alarm** + +**curr[3-4]_rated_max** + +The driver provides for voltage: input, critical and low critical thresholds +and critical and low critical alarms. +The driver exports the following attributes for via the sysfs files, where +indexes 1, 2 are for "vin" and 3, 4 for "vout": + +**in[1-4]_min** + +**in[1-4]_crit** + +**in[1-4_crit_alarm** + +**in[1-4]_input** + +**in[1-4]_label** + +**in[1-4]_max** + +**in[1-4]_max_alarm** + +**in[1-4]_min** + +**in[1-4]_min_alarm** + +**in[3-4]_lcrit** + +**in[3-4]_lcrit_alarm** + +**in[3-4]_rated_max** + +**in[3-4]_rated_min** + +The driver provides for power: input and alarms. +The driver exports the following attributes for via the sysfs files, where +indexes 1, 2 are for "pin" and 3, 4 for "pout": + +**power[1-2]_alarm** + +**power[1-4]_input** + +**power[1-4]_label** + +**power[1-4]_max** + +**power[1-4]_rated_max** + +The driver provides for temperature: input, maximum and critical thresholds +and maximum and critical alarms. +The driver exports the following attributes for via the sysfs files: + +**temp[1-2]_crit** + +**temp[1-2]_crit_alarm** + +**temp[1-2]_input** + +**temp[1-2]_max** + +**temp[1-2]_max_alarm** diff --git a/Documentation/power/energy-model.rst b/Documentation/power/energy-model.rst index 49549aab41b4..feb257b7f350 100644 --- a/Documentation/power/energy-model.rst +++ b/Documentation/power/energy-model.rst @@ -123,6 +123,26 @@ allows a platform to register EM power values which are reflecting total power (static + dynamic). These power values might be coming directly from experiments and measurements. +Registration of 'artificial' EM +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There is an option to provide a custom callback for drivers missing detailed +knowledge about power value for each performance state. The callback +.get_cost() is optional and provides the 'cost' values used by the EAS. +This is useful for platforms that only provide information on relative +efficiency between CPU types, where one could use the information to +create an abstract power model. But even an abstract power model can +sometimes be hard to fit in, given the input power value size restrictions. +The .get_cost() allows to provide the 'cost' values which reflect the +efficiency of the CPUs. This would allow to provide EAS information which +has different relation than what would be forced by the EM internal +formulas calculating 'cost' values. To register an EM for such platform, the +driver must set the flag 'milliwatts' to 0, provide .get_power() callback +and provide .get_cost() callback. The EM framework would handle such platform +properly during registration. A flag EM_PERF_DOMAIN_ARTIFICIAL is set for such +platform. Special care should be taken by other frameworks which are using EM +to test and treat this flag properly. + Registration of 'simple' EM ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -181,8 +201,8 @@ EM framework:: -> drivers/cpufreq/foo_cpufreq.c - 01 static int est_power(unsigned long *mW, unsigned long *KHz, - 02 struct device *dev) + 01 static int est_power(struct device *dev, unsigned long *mW, + 02 unsigned long *KHz) 03 { 04 long freq, power; 05 diff --git a/Documentation/security/IMA-templates.rst b/Documentation/security/IMA-templates.rst index 1a91d92950a7..15b4add314fc 100644 --- a/Documentation/security/IMA-templates.rst +++ b/Documentation/security/IMA-templates.rst @@ -66,12 +66,13 @@ descriptors by adding their identifier to the format string calculated with the SHA1 or MD5 hash algorithm; - 'n': the name of the event (i.e. the file name), with size up to 255 bytes; - 'd-ng': the digest of the event, calculated with an arbitrary hash - algorithm (field format: [:]digest, where the digest - prefix is shown only if the hash algorithm is not SHA1 or MD5); + algorithm (field format: :digest); + - 'd-ngv2': same as d-ng, but prefixed with the "ima" or "verity" digest type + (field format: ::digest); - 'd-modsig': the digest of the event without the appended modsig; - 'n-ng': the name of the event, without size limitations; - - 'sig': the file signature, or the EVM portable signature if the file - signature is not found; + - 'sig': the file signature, based on either the file's/fsverity's digest[1], + or the EVM portable signature, if 'security.ima' contains a file hash. - 'modsig' the appended file signature; - 'buf': the buffer data that was used to generate the hash without size limitations; - 'evmsig': the EVM portable signature; @@ -88,7 +89,9 @@ Below, there is the list of defined template descriptors: - "ima": its format is ``d|n``; - "ima-ng" (default): its format is ``d-ng|n-ng``; + - "ima-ngv2": its format is ``d-ngv2|n-ng``; - "ima-sig": its format is ``d-ng|n-ng|sig``; + - "ima-sigv2": its format is ``d-ngv2|n-ng|sig``; - "ima-buf": its format is ``d-ng|n-ng|buf``; - "ima-modsig": its format is ``d-ng|n-ng|sig|d-modsig|modsig``; - "evm-sig": its format is ``d-ng|n-ng|evmsig|xattrnames|xattrlengths|xattrvalues|iuid|igid|imode``; diff --git a/Documentation/security/keys/trusted-encrypted.rst b/Documentation/security/keys/trusted-encrypted.rst index f614dad7de12..0bfb4c339748 100644 --- a/Documentation/security/keys/trusted-encrypted.rst +++ b/Documentation/security/keys/trusted-encrypted.rst @@ -35,6 +35,13 @@ safe. Rooted to Hardware Unique Key (HUK) which is generally burnt in on-chip fuses and is accessible to TEE only. + (3) CAAM (Cryptographic Acceleration and Assurance Module: IP on NXP SoCs) + + When High Assurance Boot (HAB) is enabled and the CAAM is in secure + mode, trust is rooted to the OTPMK, a never-disclosed 256-bit key + randomly generated and fused into each SoC at manufacturing time. + Otherwise, a common fixed test key is used instead. + * Execution isolation (1) TPM @@ -46,6 +53,10 @@ safe. Customizable set of operations running in isolated execution environment verified via Secure/Trusted boot process. + (3) CAAM + + Fixed set of operations running in isolated execution environment. + * Optional binding to platform integrity state (1) TPM @@ -63,6 +74,11 @@ safe. Relies on Secure/Trusted boot process for platform integrity. It can be extended with TEE based measured boot process. + (3) CAAM + + Relies on the High Assurance Boot (HAB) mechanism of NXP SoCs + for platform integrity. + * Interfaces and APIs (1) TPM @@ -74,10 +90,13 @@ safe. TEEs have well-documented, standardized client interface and APIs. For more details refer to ``Documentation/staging/tee.rst``. + (3) CAAM + + Interface is specific to silicon vendor. * Threat model - The strength and appropriateness of a particular TPM or TEE for a given + The strength and appropriateness of a particular trust source for a given purpose must be assessed when using them to protect security-relevant data. @@ -87,22 +106,32 @@ Key Generation Trusted Keys ------------ -New keys are created from random numbers generated in the trust source. They -are encrypted/decrypted using a child key in the storage key hierarchy. -Encryption and decryption of the child key must be protected by a strong -access control policy within the trust source. +New keys are created from random numbers. They are encrypted/decrypted using +a child key in the storage key hierarchy. Encryption and decryption of the +child key must be protected by a strong access control policy within the +trust source. The random number generator in use differs according to the +selected trust source: - * TPM (hardware device) based RNG + * TPM: hardware device based RNG - Strength of random numbers may vary from one device manufacturer to - another. + Keys are generated within the TPM. Strength of random numbers may vary + from one device manufacturer to another. - * TEE (OP-TEE based on Arm TrustZone) based RNG + * TEE: OP-TEE based on Arm TrustZone based RNG RNG is customizable as per platform needs. It can either be direct output from platform specific hardware RNG or a software based Fortuna CSPRNG which can be seeded via multiple entropy sources. + * CAAM: Kernel RNG + + The normal kernel random number generator is used. To seed it from the + CAAM HWRNG, enable CRYPTO_DEV_FSL_CAAM_RNG_API and ensure the device + is probed. + +Users may override this by specifying ``trusted.rng=kernel`` on the kernel +command-line to override the used RNG with the kernel's random number pool. + Encrypted Keys -------------- @@ -189,6 +218,19 @@ Usage:: specific to TEE device implementation. The key length for new keys is always in bytes. Trusted Keys can be 32 - 128 bytes (256 - 1024 bits). +Trusted Keys usage: CAAM +------------------------ + +Usage:: + + keyctl add trusted name "new keylen" ring + keyctl add trusted name "load hex_blob" ring + keyctl print keyid + +"keyctl print" returns an ASCII hex copy of the sealed key, which is in a +CAAM-specific format. The key length for new keys is always in bytes. +Trusted Keys can be 32 - 128 bytes (256 - 1024 bits). + Encrypted Keys usage -------------------- diff --git a/Documentation/security/landlock.rst b/Documentation/security/landlock.rst index 3df68cb1d10f..5c77730b4479 100644 --- a/Documentation/security/landlock.rst +++ b/Documentation/security/landlock.rst @@ -7,7 +7,7 @@ Landlock LSM: kernel documentation ================================== :Author: Mickaël Salaün -:Date: March 2021 +:Date: May 2022 Landlock's goal is to create scoped access-control (i.e. sandboxing). To harden a whole system, this feature should be available to any process, @@ -42,6 +42,21 @@ Guiding principles for safe access controls * Computation related to Landlock operations (e.g. enforcing a ruleset) shall only impact the processes requesting them. +Design choices +============== + +Filesystem access rights +------------------------ + +All access rights are tied to an inode and what can be accessed through it. +Reading the content of a directory doesn't imply to be allowed to read the +content of a listed inode. Indeed, a file name is local to its parent +directory, and an inode can be referenced by multiple file names thanks to +(hard) links. Being able to unlink a file only has a direct impact on the +directory, not the unlinked inode. This is the reason why +`LANDLOCK_ACCESS_FS_REMOVE_FILE` or `LANDLOCK_ACCESS_FS_REFER` are not allowed +to be tied to files but only to directories. + Tests ===== diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst index f35552ff19ba..b8ea59493964 100644 --- a/Documentation/userspace-api/landlock.rst +++ b/Documentation/userspace-api/landlock.rst @@ -1,14 +1,14 @@ .. SPDX-License-Identifier: GPL-2.0 .. Copyright © 2017-2020 Mickaël Salaün .. Copyright © 2019-2020 ANSSI -.. Copyright © 2021 Microsoft Corporation +.. Copyright © 2021-2022 Microsoft Corporation ===================================== Landlock: unprivileged access control ===================================== :Author: Mickaël Salaün -:Date: March 2021 +:Date: May 2022 The goal of Landlock is to enable to restrict ambient rights (e.g. global filesystem access) for a set of processes. Because Landlock is a stackable @@ -18,6 +18,13 @@ is expected to help mitigate the security impact of bugs or unexpected/malicious behaviors in user space applications. Landlock empowers any process, including unprivileged ones, to securely restrict themselves. +We can quickly make sure that Landlock is enabled in the running system by +looking for "landlock: Up and running" in kernel logs (as root): ``dmesg | grep +landlock || journalctl -kg landlock`` . Developers can also easily check for +Landlock support with a :ref:`related system call `. If +Landlock is not currently supported, we need to :ref:`configure the kernel +appropriately `. + Landlock rules ============== @@ -29,14 +36,15 @@ the thread enforcing it, and its future children. Defining and enforcing a security policy ---------------------------------------- -We first need to create the ruleset that will contain our rules. For this +We first need to define the ruleset that will contain our rules. For this example, the ruleset will contain rules that only allow read actions, but write actions will be denied. The ruleset then needs to handle both of these kind of -actions. +actions. This is required for backward and forward compatibility (i.e. the +kernel and user space may not know each other's supported restrictions), hence +the need to be explicit about the denied-by-default access rights. .. code-block:: c - int ruleset_fd; struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE | @@ -51,9 +59,34 @@ actions. LANDLOCK_ACCESS_FS_MAKE_SOCK | LANDLOCK_ACCESS_FS_MAKE_FIFO | LANDLOCK_ACCESS_FS_MAKE_BLOCK | - LANDLOCK_ACCESS_FS_MAKE_SYM, + LANDLOCK_ACCESS_FS_MAKE_SYM | + LANDLOCK_ACCESS_FS_REFER, }; +Because we may not know on which kernel version an application will be +executed, it is safer to follow a best-effort security approach. Indeed, we +should try to protect users as much as possible whatever the kernel they are +using. To avoid binary enforcement (i.e. either all security features or +none), we can leverage a dedicated Landlock command to get the current version +of the Landlock ABI and adapt the handled accesses. Let's check if we should +remove the `LANDLOCK_ACCESS_FS_REFER` access right which is only supported +starting with the second version of the ABI. + +.. code-block:: c + + int abi; + + abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); + if (abi < 2) { + ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER; + } + +This enables to create an inclusive ruleset that will contain our rules. + +.. code-block:: c + + int ruleset_fd; + ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); if (ruleset_fd < 0) { perror("Failed to create a ruleset"); @@ -92,6 +125,11 @@ descriptor. return 1; } +It may also be required to create rules following the same logic as explained +for the ruleset creation, by filtering access rights according to the Landlock +ABI version. In this example, this is not required because +`LANDLOCK_ACCESS_FS_REFER` is not allowed by any rule. + We now have a ruleset with one rule allowing read access to ``/usr`` while denying all other handled accesses for the filesystem. The next step is to restrict the current thread from gaining more privileges (e.g. thanks to a SUID @@ -125,6 +163,27 @@ ruleset. Full working code can be found in `samples/landlock/sandboxer.c`_. +Good practices +-------------- + +It is recommended setting access rights to file hierarchy leaves as much as +possible. For instance, it is better to be able to have ``~/doc/`` as a +read-only hierarchy and ``~/tmp/`` as a read-write hierarchy, compared to +``~/`` as a read-only hierarchy and ``~/tmp/`` as a read-write hierarchy. +Following this good practice leads to self-sufficient hierarchies that don't +depend on their location (i.e. parent directories). This is particularly +relevant when we want to allow linking or renaming. Indeed, having consistent +access rights per directory enables to change the location of such directory +without relying on the destination directory access rights (except those that +are required for this operation, see `LANDLOCK_ACCESS_FS_REFER` documentation). +Having self-sufficient hierarchies also helps to tighten the required access +rights to the minimal set of data. This also helps avoid sinkhole directories, +i.e. directories where data can be linked to but not linked from. However, +this depends on data organization, which might not be controlled by developers. +In this case, granting read-write access to ``~/tmp/``, instead of write-only +access, would potentially allow to move ``~/tmp/`` to a non-readable directory +and still keep the ability to list the content of ``~/tmp/``. + Layers of file path access rights --------------------------------- @@ -192,6 +251,58 @@ To be allowed to use :manpage:`ptrace(2)` and related syscalls on a target process, a sandboxed process should have a subset of the target process rules, which means the tracee must be in a sub-domain of the tracer. +Compatibility +============= + +Backward and forward compatibility +---------------------------------- + +Landlock is designed to be compatible with past and future versions of the +kernel. This is achieved thanks to the system call attributes and the +associated bitflags, particularly the ruleset's `handled_access_fs`. Making +handled access right explicit enables the kernel and user space to have a clear +contract with each other. This is required to make sure sandboxing will not +get stricter with a system update, which could break applications. + +Developers can subscribe to the `Landlock mailing list +`_ to knowingly update and +test their applications with the latest available features. In the interest of +users, and because they may use different kernel versions, it is strongly +encouraged to follow a best-effort security approach by checking the Landlock +ABI version at runtime and only enforcing the supported features. + +.. _landlock_abi_versions: + +Landlock ABI versions +--------------------- + +The Landlock ABI version can be read with the sys_landlock_create_ruleset() +system call: + +.. code-block:: c + + int abi; + + abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); + if (abi < 0) { + switch (errno) { + case ENOSYS: + printf("Landlock is not supported by the current kernel.\n"); + break; + case EOPNOTSUPP: + printf("Landlock is currently disabled.\n"); + break; + } + return 0; + } + if (abi >= 2) { + printf("Landlock supports LANDLOCK_ACCESS_FS_REFER.\n"); + } + +The following kernel interfaces are implicitly supported by the first ABI +version. Features only supported from a specific version are explicitly marked +as such. + Kernel interface ================ @@ -228,21 +339,6 @@ Enforcing a ruleset Current limitations =================== -File renaming and linking -------------------------- - -Because Landlock targets unprivileged access controls, it is needed to properly -handle composition of rules. Such property also implies rules nesting. -Properly handling multiple layers of ruleset, each one of them able to restrict -access to files, also implies to inherit the ruleset restrictions from a parent -to its hierarchy. Because files are identified and restricted by their -hierarchy, moving or linking a file from one directory to another implies to -propagate the hierarchy constraints. To protect against privilege escalations -through renaming or linking, and for the sake of simplicity, Landlock currently -limits linking and renaming to the same directory. Future Landlock evolutions -will enable more flexibility for renaming and linking, with dedicated ruleset -flags. - Filesystem topology modification -------------------------------- @@ -267,8 +363,8 @@ restrict such paths with dedicated ruleset flags. Ruleset layers -------------- -There is a limit of 64 layers of stacked rulesets. This can be an issue for a -task willing to enforce a new ruleset in complement to its 64 inherited +There is a limit of 16 layers of stacked rulesets. This can be an issue for a +task willing to enforce a new ruleset in complement to its 16 inherited rulesets. Once this limit is reached, sys_landlock_restrict_self() returns E2BIG. It is then strongly suggested to carefully build rulesets once in the life of a thread, especially for applications able to launch other applications @@ -281,6 +377,44 @@ Memory usage Kernel memory allocated to create rulesets is accounted and can be restricted by the Documentation/admin-guide/cgroup-v1/memory.rst. +Previous limitations +==================== + +File renaming and linking (ABI 1) +--------------------------------- + +Because Landlock targets unprivileged access controls, it needs to properly +handle composition of rules. Such property also implies rules nesting. +Properly handling multiple layers of rulesets, each one of them able to +restrict access to files, also implies inheritance of the ruleset restrictions +from a parent to its hierarchy. Because files are identified and restricted by +their hierarchy, moving or linking a file from one directory to another implies +propagation of the hierarchy constraints, or restriction of these actions +according to the potentially lost constraints. To protect against privilege +escalations through renaming or linking, and for the sake of simplicity, +Landlock previously limited linking and renaming to the same directory. +Starting with the Landlock ABI version 2, it is now possible to securely +control renaming and linking thanks to the new `LANDLOCK_ACCESS_FS_REFER` +access right. + +.. _kernel_support: + +Kernel support +============== + +Landlock was first introduced in Linux 5.13 but it must be configured at build +time with `CONFIG_SECURITY_LANDLOCK=y`. Landlock must also be enabled at boot +time as the other security modules. The list of security modules enabled by +default is set with `CONFIG_LSM`. The kernel configuration should then +contains `CONFIG_LSM=landlock,[...]` with `[...]` as the list of other +potentially useful security modules for the running system (see the +`CONFIG_LSM` help). + +If the running kernel doesn't have `landlock` in `CONFIG_LSM`, then we can +still enable it by adding ``lsm=landlock,[...]`` to +Documentation/admin-guide/kernel-parameters.rst thanks to the bootloader +configuration. + Questions and answers ===================== diff --git a/MAINTAINERS b/MAINTAINERS index 51cca318bdca..d38d0dafd4ed 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1044,7 +1044,6 @@ F: arch/arm64/boot/dts/amd/amd-seattle-xgbe*.dtsi F: drivers/net/ethernet/amd/xgbe/ AMD SENSOR FUSION HUB DRIVER -M: Nehal Shah M: Basavaraj Natikar L: linux-input@vger.kernel.org S: Maintained @@ -1447,6 +1446,7 @@ F: drivers/media/i2c/aptina-pll.* AQUACOMPUTER D5 NEXT PUMP SENSOR DRIVER M: Aleksa Savic +M: Jack Doan L: linux-hwmon@vger.kernel.org S: Maintained F: Documentation/hwmon/aquacomputer_d5next.rst @@ -3101,6 +3101,16 @@ S: Maintained F: Documentation/devicetree/bindings/mmc/aspeed,sdhci.yaml F: drivers/mmc/host/sdhci-of-aspeed* +ASPEED SMC SPI DRIVER +M: Chin-Ting Kuo +M: Cédric Le Goater +L: linux-aspeed@lists.ozlabs.org (moderated for non-subscribers) +L: openbmc@lists.ozlabs.org (moderated for non-subscribers) +L: linux-spi@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/spi/aspeed,ast2600-fmc.yaml +F: drivers/spi/spi-aspeed-smc.c + ASPEED VIDEO ENGINE DRIVER M: Eddie James L: linux-media@vger.kernel.org @@ -4575,7 +4585,9 @@ L: keyrings@vger.kernel.org S: Maintained F: Documentation/admin-guide/module-signing.rst F: certs/ +F: scripts/check-blacklist-hashes.awk F: scripts/sign-file.c +F: tools/certs/ CFAG12864B LCD DRIVER M: Miguel Ojeda @@ -8753,6 +8765,14 @@ F: drivers/hid/hid-sensor-* F: drivers/iio/*/hid-* F: include/linux/hid-sensor-* +HID WACOM DRIVER +M: Ping Cheng +M: Jason Gerecke +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/wacom.h +F: drivers/hid/wacom_* + HIGH-RESOLUTION TIMERS, CLOCKEVENTS M: Thomas Gleixner L: linux-kernel@vger.kernel.org @@ -10872,6 +10892,15 @@ S: Supported F: include/keys/trusted_tee.h F: security/keys/trusted-keys/trusted_tee.c +KEYS-TRUSTED-CAAM +M: Ahmad Fatoum +R: Pengutronix Kernel Team +L: linux-integrity@vger.kernel.org +L: keyrings@vger.kernel.org +S: Maintained +F: include/keys/trusted_caam.h +F: security/keys/trusted-keys/trusted_caam.c + KEYS/KEYRINGS M: David Howells M: Jarkko Sakkinen @@ -11844,7 +11873,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER M: Hu Ziji L: linux-mmc@vger.kernel.org S: Supported -F: Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt +F: Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.yaml F: drivers/mmc/host/sdhci-xenon* MATROX FRAMEBUFFER DRIVER @@ -13550,12 +13579,21 @@ M: Samuel Mendoza-Jonas S: Maintained F: net/ncsi/ -NCT6775 HARDWARE MONITOR DRIVER +NCT6775 HARDWARE MONITOR DRIVER - CORE & PLATFORM DRIVER M: Guenter Roeck L: linux-hwmon@vger.kernel.org S: Maintained F: Documentation/hwmon/nct6775.rst -F: drivers/hwmon/nct6775.c +F: drivers/hwmon/nct6775-core.c +F: drivers/hwmon/nct6775-platform.c +F: drivers/hwmon/nct6775.h + +NCT6775 HARDWARE MONITOR DRIVER - I2C DRIVER +M: Zev Weiss +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/hwmon/nuvoton,nct6775.yaml +F: drivers/hwmon/nct6775-i2c.c NETDEVSIM M: Jakub Kicinski @@ -19857,6 +19895,7 @@ F: drivers/media/usb/tm6000/ TMIO/SDHI MMC DRIVER M: Wolfram Sang L: linux-mmc@vger.kernel.org +L: linux-renesas-soc@vger.kernel.org S: Supported F: drivers/mmc/host/renesas_sdhi* F: drivers/mmc/host/tmio_mmc* @@ -19866,6 +19905,7 @@ TMP401 HARDWARE MONITOR DRIVER M: Guenter Roeck L: linux-hwmon@vger.kernel.org S: Maintained +F: Documentation/devicetree/bindings/hwmon/ti,tmp401.yaml F: Documentation/hwmon/tmp401.rst F: drivers/hwmon/tmp401.c diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c index 709986b9fb6b..d388b97b2644 100644 --- a/arch/arm64/kernel/smp.c +++ b/arch/arm64/kernel/smp.c @@ -517,6 +517,7 @@ struct acpi_madt_generic_interrupt *acpi_cpu_get_madt_gicc(int cpu) { return &cpu_madt_gicc[cpu]; } +EXPORT_SYMBOL_GPL(acpi_cpu_get_madt_gicc); /* * acpi_map_gic_cpu_interface - parse processor MADT entry diff --git a/arch/sh/boards/board-sh7757lcr.c b/arch/sh/boards/board-sh7757lcr.c index c32b4c6229d3..f39c8196efdf 100644 --- a/arch/sh/boards/board-sh7757lcr.c +++ b/arch/sh/boards/board-sh7757lcr.c @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/sh/boards/mach-ecovec24/setup.c b/arch/sh/boards/mach-ecovec24/setup.c index 4c9522dd351f..674da7ebd8b7 100644 --- a/arch/sh/boards/mach-ecovec24/setup.c +++ b/arch/sh/boards/mach-ecovec24/setup.c @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/sh/boot/romimage/mmcif-sh7724.c b/arch/sh/boot/romimage/mmcif-sh7724.c index 6595b6b45bf1..d30123d859e0 100644 --- a/arch/sh/boot/romimage/mmcif-sh7724.c +++ b/arch/sh/boot/romimage/mmcif-sh7724.c @@ -8,7 +8,7 @@ * for more details. */ -#include +#include #include #define MMCIF_BASE (void __iomem *)0xa4ca0000 diff --git a/arch/x86/include/asm/msr-index.h b/arch/x86/include/asm/msr-index.h index 14154b8b1720..403e83b4adc8 100644 --- a/arch/x86/include/asm/msr-index.h +++ b/arch/x86/include/asm/msr-index.h @@ -319,6 +319,7 @@ /* Run Time Average Power Limiting (RAPL) Interface */ +#define MSR_VR_CURRENT_CONFIG 0x00000601 #define MSR_RAPL_POWER_UNIT 0x00000606 #define MSR_PKG_POWER_LIMIT 0x00000610 diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c index 6d2c50819501..907cc98b1938 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c @@ -435,7 +435,7 @@ static void __init mp_override_legacy_irq(u8 bus_irq, u8 polarity, u8 trigger, isa_irq_to_gsi[bus_irq] = gsi; } -static int mp_config_acpi_gsi(struct device *dev, u32 gsi, int trigger, +static void mp_config_acpi_gsi(struct device *dev, u32 gsi, int trigger, int polarity) { #ifdef CONFIG_X86_MPPARSE @@ -447,9 +447,9 @@ static int mp_config_acpi_gsi(struct device *dev, u32 gsi, int trigger, u8 pin; if (!acpi_ioapic) - return 0; + return; if (!dev || !dev_is_pci(dev)) - return 0; + return; pdev = to_pci_dev(dev); number = pdev->bus->number; @@ -468,7 +468,6 @@ static int mp_config_acpi_gsi(struct device *dev, u32 gsi, int trigger, mp_save_irq(&mp_irq); #endif - return 0; } static int __init mp_register_ioapic_irq(u8 bus_irq, u8 polarity, @@ -1863,7 +1862,7 @@ int __acpi_release_global_lock(unsigned int *lock) void __init arch_reserve_mem_area(acpi_physical_address addr, size_t size) { - e820__range_add(addr, size, E820_TYPE_ACPI); + e820__range_add(addr, size, E820_TYPE_NVS); e820__update_table_print(); } diff --git a/arch/x86/kernel/cpu/mce/apei.c b/arch/x86/kernel/cpu/mce/apei.c index 0e3ae64d3b76..717192915f28 100644 --- a/arch/x86/kernel/cpu/mce/apei.c +++ b/arch/x86/kernel/cpu/mce/apei.c @@ -177,16 +177,14 @@ retry: /* no more record */ if (*record_id == APEI_ERST_INVALID_RECORD_ID) goto out; - rc = erst_read(*record_id, &rcd.hdr, sizeof(rcd)); + rc = erst_read_record(*record_id, &rcd.hdr, sizeof(rcd), sizeof(rcd), + &CPER_CREATOR_MCE); /* someone else has cleared the record, try next one */ if (rc == -ENOENT) goto retry; else if (rc < 0) goto out; - /* try to skip other type records in storage */ - else if (rc != sizeof(rcd) || - !guid_equal(&rcd.hdr.creator_id, &CPER_CREATOR_MCE)) - goto retry; + memcpy(m, &rcd.mce, sizeof(*m)); rc = sizeof(*m); out: diff --git a/certs/.gitignore b/certs/.gitignore index 9e42fe3e02f5..56637aceaf81 100644 --- a/certs/.gitignore +++ b/certs/.gitignore @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +/blacklist_hashes_checked /extract-cert /x509_certificate_list /x509_revocation_list diff --git a/certs/Kconfig b/certs/Kconfig index 73d1350c223a..476755703cf8 100644 --- a/certs/Kconfig +++ b/certs/Kconfig @@ -104,8 +104,11 @@ config SYSTEM_BLACKLIST_HASH_LIST help If set, this option should be the filename of a list of hashes in the form "", "", ... . This will be included into a C - wrapper to incorporate the list into the kernel. Each should - be a string of hex digits. + wrapper to incorporate the list into the kernel. Each must be a + string starting with a prefix ("tbs" or "bin"), then a colon (":"), and + finally an even number of hexadecimal lowercase characters (up to 128). + Certificate hashes can be generated with + tools/certs/print-cert-tbs-hash.sh . config SYSTEM_REVOCATION_LIST bool "Provide system-wide ring of revocation certificates" @@ -124,4 +127,14 @@ config SYSTEM_REVOCATION_KEYS containing X.509 certificates to be included in the default blacklist keyring. +config SYSTEM_BLACKLIST_AUTH_UPDATE + bool "Allow root to add signed blacklist keys" + depends on SYSTEM_BLACKLIST_KEYRING + depends on SYSTEM_DATA_VERIFICATION + help + If set, provide the ability to load new blacklist keys at run time if + they are signed and vouched by a certificate from the builtin trusted + keyring. The PKCS#7 signature of the description is set in the key + payload. Blacklist keys cannot be removed. + endmenu diff --git a/certs/Makefile b/certs/Makefile index d8443cfb1c40..1d26ae36af20 100644 --- a/certs/Makefile +++ b/certs/Makefile @@ -7,6 +7,18 @@ obj-$(CONFIG_SYSTEM_TRUSTED_KEYRING) += system_keyring.o system_certificates.o c obj-$(CONFIG_SYSTEM_BLACKLIST_KEYRING) += blacklist.o common.o obj-$(CONFIG_SYSTEM_REVOCATION_LIST) += revocation_certificates.o ifneq ($(CONFIG_SYSTEM_BLACKLIST_HASH_LIST),) +quiet_cmd_check_blacklist_hashes = CHECK $(patsubst "%",%,$(2)) + cmd_check_blacklist_hashes = $(AWK) -f $(srctree)/scripts/check-blacklist-hashes.awk $(2); touch $@ + +$(eval $(call config_filename,SYSTEM_BLACKLIST_HASH_LIST)) + +$(obj)/blacklist_hashes.o: $(obj)/blacklist_hashes_checked + +CFLAGS_blacklist_hashes.o += -I$(srctree) + +targets += blacklist_hashes_checked +$(obj)/blacklist_hashes_checked: $(SYSTEM_BLACKLIST_HASH_LIST_SRCPREFIX)$(SYSTEM_BLACKLIST_HASH_LIST_FILENAME) scripts/check-blacklist-hashes.awk FORCE + $(call if_changed,check_blacklist_hashes,$(SYSTEM_BLACKLIST_HASH_LIST_SRCPREFIX)$(CONFIG_SYSTEM_BLACKLIST_HASH_LIST)) obj-$(CONFIG_SYSTEM_BLACKLIST_KEYRING) += blacklist_hashes.o else obj-$(CONFIG_SYSTEM_BLACKLIST_KEYRING) += blacklist_nohashes.o @@ -21,7 +33,7 @@ $(obj)/system_certificates.o: $(obj)/x509_certificate_list $(obj)/x509_certificate_list: $(CONFIG_SYSTEM_TRUSTED_KEYS) $(obj)/extract-cert FORCE $(call if_changed,extract_certs) -targets += x509_certificate_list +targets += x509_certificate_list blacklist_hashes_checked # If module signing is requested, say by allyesconfig, but a key has not been # supplied, then one will need to be generated to make sure the build does not diff --git a/certs/blacklist.c b/certs/blacklist.c index c9a435b15af4..25094ea73600 100644 --- a/certs/blacklist.c +++ b/certs/blacklist.c @@ -15,10 +15,24 @@ #include #include #include +#include #include #include "blacklist.h" #include "common.h" +/* + * According to crypto/asymmetric_keys/x509_cert_parser.c:x509_note_pkey_algo(), + * the size of the currently longest supported hash algorithm is 512 bits, + * which translates into 128 hex characters. + */ +#define MAX_HASH_LEN 128 + +#define BLACKLIST_KEY_PERM (KEY_POS_SEARCH | KEY_POS_VIEW | \ + KEY_USR_SEARCH | KEY_USR_VIEW) + +static const char tbs_prefix[] = "tbs"; +static const char bin_prefix[] = "bin"; + static struct key *blacklist_keyring; #ifdef CONFIG_SYSTEM_REVOCATION_LIST @@ -32,41 +46,89 @@ extern __initconst const unsigned long revocation_certificate_list_size; */ static int blacklist_vet_description(const char *desc) { - int n = 0; + int i, prefix_len, tbs_step = 0, bin_step = 0; - if (*desc == ':') - return -EINVAL; - for (; *desc; desc++) - if (*desc == ':') - goto found_colon; + /* The following algorithm only works if prefix lengths match. */ + BUILD_BUG_ON(sizeof(tbs_prefix) != sizeof(bin_prefix)); + prefix_len = sizeof(tbs_prefix) - 1; + for (i = 0; *desc; desc++, i++) { + if (*desc == ':') { + if (tbs_step == prefix_len) + goto found_colon; + if (bin_step == prefix_len) + goto found_colon; + return -EINVAL; + } + if (i >= prefix_len) + return -EINVAL; + if (*desc == tbs_prefix[i]) + tbs_step++; + if (*desc == bin_prefix[i]) + bin_step++; + } return -EINVAL; found_colon: desc++; - for (; *desc; desc++) { + for (i = 0; *desc && i < MAX_HASH_LEN; desc++, i++) { if (!isxdigit(*desc) || isupper(*desc)) return -EINVAL; - n++; } + if (*desc) + /* The hash is greater than MAX_HASH_LEN. */ + return -ENOPKG; - if (n == 0 || n & 1) + /* Checks for an even number of hexadecimal characters. */ + if (i == 0 || i & 1) return -EINVAL; return 0; } -/* - * The hash to be blacklisted is expected to be in the description. There will - * be no payload. - */ -static int blacklist_preparse(struct key_preparsed_payload *prep) +static int blacklist_key_instantiate(struct key *key, + struct key_preparsed_payload *prep) { - if (prep->datalen > 0) - return -EINVAL; - return 0; +#ifdef CONFIG_SYSTEM_BLACKLIST_AUTH_UPDATE + int err; +#endif + + /* Sets safe default permissions for keys loaded by user space. */ + key->perm = BLACKLIST_KEY_PERM; + + /* + * Skips the authentication step for builtin hashes, they are not + * signed but still trusted. + */ + if (key->flags & (1 << KEY_FLAG_BUILTIN)) + goto out; + +#ifdef CONFIG_SYSTEM_BLACKLIST_AUTH_UPDATE + /* + * Verifies the description's PKCS#7 signature against the builtin + * trusted keyring. + */ + err = verify_pkcs7_signature(key->description, + strlen(key->description), prep->data, prep->datalen, + NULL, VERIFYING_UNSPECIFIED_SIGNATURE, NULL, NULL); + if (err) + return err; +#else + /* + * It should not be possible to come here because the keyring doesn't + * have KEY_USR_WRITE and the only other way to call this function is + * for builtin hashes. + */ + WARN_ON_ONCE(1); + return -EPERM; +#endif + +out: + return generic_key_instantiate(key, prep); } -static void blacklist_free_preparse(struct key_preparsed_payload *prep) +static int blacklist_key_update(struct key *key, + struct key_preparsed_payload *prep) { + return -EPERM; } static void blacklist_describe(const struct key *key, struct seq_file *m) @@ -77,17 +139,48 @@ static void blacklist_describe(const struct key *key, struct seq_file *m) static struct key_type key_type_blacklist = { .name = "blacklist", .vet_description = blacklist_vet_description, - .preparse = blacklist_preparse, - .free_preparse = blacklist_free_preparse, - .instantiate = generic_key_instantiate, + .instantiate = blacklist_key_instantiate, + .update = blacklist_key_update, .describe = blacklist_describe, }; +static char *get_raw_hash(const u8 *hash, size_t hash_len, + enum blacklist_hash_type hash_type) +{ + size_t type_len; + const char *type_prefix; + char *buffer, *p; + + switch (hash_type) { + case BLACKLIST_HASH_X509_TBS: + type_len = sizeof(tbs_prefix) - 1; + type_prefix = tbs_prefix; + break; + case BLACKLIST_HASH_BINARY: + type_len = sizeof(bin_prefix) - 1; + type_prefix = bin_prefix; + break; + default: + WARN_ON_ONCE(1); + return ERR_PTR(-EINVAL); + } + buffer = kmalloc(type_len + 1 + hash_len * 2 + 1, GFP_KERNEL); + if (!buffer) + return ERR_PTR(-ENOMEM); + p = memcpy(buffer, type_prefix, type_len); + p += type_len; + *p++ = ':'; + bin2hex(p, hash, hash_len); + p += hash_len * 2; + *p = '\0'; + return buffer; +} + /** - * mark_hash_blacklisted - Add a hash to the system blacklist + * mark_raw_hash_blacklisted - Add a hash to the system blacklist * @hash: The hash as a hex string with a type prefix (eg. "tbs:23aa429783") */ -int mark_hash_blacklisted(const char *hash) +static int mark_raw_hash_blacklisted(const char *hash) { key_ref_t key; @@ -96,8 +189,7 @@ int mark_hash_blacklisted(const char *hash) hash, NULL, 0, - ((KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW), + BLACKLIST_KEY_PERM, KEY_ALLOC_NOT_IN_QUOTA | KEY_ALLOC_BUILT_IN); if (IS_ERR(key)) { @@ -107,29 +199,36 @@ int mark_hash_blacklisted(const char *hash) return 0; } +int mark_hash_blacklisted(const u8 *hash, size_t hash_len, + enum blacklist_hash_type hash_type) +{ + const char *buffer; + int err; + + buffer = get_raw_hash(hash, hash_len, hash_type); + if (IS_ERR(buffer)) + return PTR_ERR(buffer); + err = mark_raw_hash_blacklisted(buffer); + kfree(buffer); + return err; +} + /** * is_hash_blacklisted - Determine if a hash is blacklisted * @hash: The hash to be checked as a binary blob * @hash_len: The length of the binary hash - * @type: Type of hash + * @hash_type: Type of hash */ -int is_hash_blacklisted(const u8 *hash, size_t hash_len, const char *type) +int is_hash_blacklisted(const u8 *hash, size_t hash_len, + enum blacklist_hash_type hash_type) { key_ref_t kref; - size_t type_len = strlen(type); - char *buffer, *p; + const char *buffer; int ret = 0; - buffer = kmalloc(type_len + 1 + hash_len * 2 + 1, GFP_KERNEL); - if (!buffer) - return -ENOMEM; - p = memcpy(buffer, type, type_len); - p += type_len; - *p++ = ':'; - bin2hex(p, hash, hash_len); - p += hash_len * 2; - *p = 0; - + buffer = get_raw_hash(hash, hash_len, hash_type); + if (IS_ERR(buffer)) + return PTR_ERR(buffer); kref = keyring_search(make_key_ref(blacklist_keyring, true), &key_type_blacklist, buffer, false); if (!IS_ERR(kref)) { @@ -144,7 +243,8 @@ EXPORT_SYMBOL_GPL(is_hash_blacklisted); int is_binary_blacklisted(const u8 *hash, size_t hash_len) { - if (is_hash_blacklisted(hash, hash_len, "bin") == -EKEYREJECTED) + if (is_hash_blacklisted(hash, hash_len, BLACKLIST_HASH_BINARY) == + -EKEYREJECTED) return -EPERM; return 0; @@ -166,8 +266,10 @@ int add_key_to_revocation_list(const char *data, size_t size) NULL, data, size, - ((KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW), - KEY_ALLOC_NOT_IN_QUOTA | KEY_ALLOC_BUILT_IN); + KEY_POS_VIEW | KEY_POS_READ | KEY_POS_SEARCH + | KEY_USR_VIEW, + KEY_ALLOC_NOT_IN_QUOTA | KEY_ALLOC_BUILT_IN + | KEY_ALLOC_BYPASS_RESTRICTION); if (IS_ERR(key)) { pr_err("Problem with revocation key (%ld)\n", PTR_ERR(key)); @@ -194,30 +296,57 @@ int is_key_on_revocation_list(struct pkcs7_message *pkcs7) } #endif +static int restrict_link_for_blacklist(struct key *dest_keyring, + const struct key_type *type, const union key_payload *payload, + struct key *restrict_key) +{ + if (type == &key_type_blacklist) + return 0; + return -EOPNOTSUPP; +} + /* * Initialise the blacklist + * + * The blacklist_init() function is registered as an initcall via + * device_initcall(). As a result if the blacklist_init() function fails for + * any reason the kernel continues to execute. While cleanly returning -ENODEV + * could be acceptable for some non-critical kernel parts, if the blacklist + * keyring fails to load it defeats the certificate/key based deny list for + * signed modules. If a critical piece of security functionality that users + * expect to be present fails to initialize, panic()ing is likely the right + * thing to do. */ static int __init blacklist_init(void) { const char *const *bl; + struct key_restriction *restriction; if (register_key_type(&key_type_blacklist) < 0) panic("Can't allocate system blacklist key type\n"); + restriction = kzalloc(sizeof(*restriction), GFP_KERNEL); + if (!restriction) + panic("Can't allocate blacklist keyring restriction\n"); + restriction->check = restrict_link_for_blacklist; + blacklist_keyring = keyring_alloc(".blacklist", GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, current_cred(), - (KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW | KEY_USR_READ | - KEY_USR_SEARCH, - KEY_ALLOC_NOT_IN_QUOTA | + KEY_POS_VIEW | KEY_POS_READ | KEY_POS_SEARCH | + KEY_POS_WRITE | + KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH +#ifdef CONFIG_SYSTEM_BLACKLIST_AUTH_UPDATE + | KEY_USR_WRITE +#endif + , KEY_ALLOC_NOT_IN_QUOTA | KEY_ALLOC_SET_KEEP, - NULL, NULL); + restriction, NULL); if (IS_ERR(blacklist_keyring)) panic("Can't allocate system blacklist keyring\n"); for (bl = blacklist_hashes; *bl; bl++) - if (mark_hash_blacklisted(*bl) < 0) + if (mark_raw_hash_blacklisted(*bl) < 0) pr_err("- blacklisting failed\n"); return 0; } diff --git a/crypto/asymmetric_keys/x509_public_key.c b/crypto/asymmetric_keys/x509_public_key.c index 91a4ad50dea2..77ed4e93ad56 100644 --- a/crypto/asymmetric_keys/x509_public_key.c +++ b/crypto/asymmetric_keys/x509_public_key.c @@ -69,7 +69,8 @@ int x509_get_sig_params(struct x509_certificate *cert) if (ret < 0) goto error_2; - ret = is_hash_blacklisted(sig->digest, sig->digest_size, "tbs"); + ret = is_hash_blacklisted(sig->digest, sig->digest_size, + BLACKLIST_HASH_X509_TBS); if (ret == -EKEYREJECTED) { pr_err("Cert %*phN is blacklisted\n", sig->digest_size, sig->digest); diff --git a/drivers/acpi/acpica/acapps.h b/drivers/acpi/acpica/acapps.h index 725e2f65cdca..0a50b4912515 100644 --- a/drivers/acpi/acpica/acapps.h +++ b/drivers/acpi/acpica/acapps.h @@ -3,7 +3,7 @@ * * Module Name: acapps - common include for ACPI applications/tools * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ @@ -17,7 +17,7 @@ /* Common info for tool signons */ #define ACPICA_NAME "Intel ACPI Component Architecture" -#define ACPICA_COPYRIGHT "Copyright (c) 2000 - 2021 Intel Corporation" +#define ACPICA_COPYRIGHT "Copyright (c) 2000 - 2022 Intel Corporation" #if ACPI_MACHINE_WIDTH == 64 #define ACPI_WIDTH " (64-bit version)" diff --git a/drivers/acpi/acpica/accommon.h b/drivers/acpi/acpica/accommon.h index be3826f46f88..bb329e34ee7d 100644 --- a/drivers/acpi/acpica/accommon.h +++ b/drivers/acpi/acpica/accommon.h @@ -3,7 +3,7 @@ * * Name: accommon.h - Common include files for generation of ACPICA source * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/acconvert.h b/drivers/acpi/acpica/acconvert.h index 53b41c7a6119..476d21e67767 100644 --- a/drivers/acpi/acpica/acconvert.h +++ b/drivers/acpi/acpica/acconvert.h @@ -3,7 +3,7 @@ * * Module Name: acapps - common include for ACPI applications/tools * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/acdebug.h b/drivers/acpi/acpica/acdebug.h index 3ccc7b2a76f1..d629716aa5b2 100644 --- a/drivers/acpi/acpica/acdebug.h +++ b/drivers/acpi/acpica/acdebug.h @@ -3,7 +3,7 @@ * * Name: acdebug.h - ACPI/AML debugger * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/acdispat.h b/drivers/acpi/acpica/acdispat.h index 3170a24fe505..fe2c3630a38d 100644 --- a/drivers/acpi/acpica/acdispat.h +++ b/drivers/acpi/acpica/acdispat.h @@ -3,7 +3,7 @@ * * Name: acdispat.h - dispatcher (parser to interpreter interface) * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/acevents.h b/drivers/acpi/acpica/acevents.h index b29ba436944a..922f559a3e59 100644 --- a/drivers/acpi/acpica/acevents.h +++ b/drivers/acpi/acpica/acevents.h @@ -3,7 +3,7 @@ * * Name: acevents.h - Event subcomponent prototypes and defines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/acglobal.h b/drivers/acpi/acpica/acglobal.h index 4366d36ef119..088d6a7d052c 100644 --- a/drivers/acpi/acpica/acglobal.h +++ b/drivers/acpi/acpica/acglobal.h @@ -3,7 +3,7 @@ * * Name: acglobal.h - Declarations for global variables * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/achware.h b/drivers/acpi/acpica/achware.h index 810de0b4c125..6f2787506b50 100644 --- a/drivers/acpi/acpica/achware.h +++ b/drivers/acpi/acpica/achware.h @@ -3,7 +3,7 @@ * * Name: achware.h -- hardware specific interfaces * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/acinterp.h b/drivers/acpi/acpica/acinterp.h index 816a16e1fc4c..6bdf133a2767 100644 --- a/drivers/acpi/acpica/acinterp.h +++ b/drivers/acpi/acpica/acinterp.h @@ -3,7 +3,7 @@ * * Name: acinterp.h - Interpreter subcomponent prototypes and defines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/aclocal.h b/drivers/acpi/acpica/aclocal.h index be57436182a1..901b1543b869 100644 --- a/drivers/acpi/acpica/aclocal.h +++ b/drivers/acpi/acpica/aclocal.h @@ -3,7 +3,7 @@ * * Name: aclocal.h - Internal data types used across the ACPI subsystem * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/acmacros.h b/drivers/acpi/acpica/acmacros.h index 93bd2d19c156..2f3e609df47d 100644 --- a/drivers/acpi/acpica/acmacros.h +++ b/drivers/acpi/acpica/acmacros.h @@ -3,7 +3,7 @@ * * Name: acmacros.h - C macros for the entire subsystem. * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/acnamesp.h b/drivers/acpi/acpica/acnamesp.h index 199aabac3790..7b27b9cc5916 100644 --- a/drivers/acpi/acpica/acnamesp.h +++ b/drivers/acpi/acpica/acnamesp.h @@ -3,7 +3,7 @@ * * Name: acnamesp.h - Namespace subcomponent prototypes and defines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/acobject.h b/drivers/acpi/acpica/acobject.h index 0aa0d847cb25..6af5dc995085 100644 --- a/drivers/acpi/acpica/acobject.h +++ b/drivers/acpi/acpica/acobject.h @@ -3,7 +3,7 @@ * * Name: acobject.h - Definition of union acpi_operand_object (Internal object only) * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/acopcode.h b/drivers/acpi/acpica/acopcode.h index c3f12ee9fc6f..a224926bd9c8 100644 --- a/drivers/acpi/acpica/acopcode.h +++ b/drivers/acpi/acpica/acopcode.h @@ -3,7 +3,7 @@ * * Name: acopcode.h - AML opcode information for the AML parser and interpreter * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/acparser.h b/drivers/acpi/acpica/acparser.h index 8e40e5909458..4511c2bd8bc3 100644 --- a/drivers/acpi/acpica/acparser.h +++ b/drivers/acpi/acpica/acparser.h @@ -3,7 +3,7 @@ * * Module Name: acparser.h - AML Parser subcomponent prototypes and defines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/acpredef.h b/drivers/acpi/acpica/acpredef.h index 5951b433c304..f7d65a20026b 100644 --- a/drivers/acpi/acpica/acpredef.h +++ b/drivers/acpi/acpica/acpredef.h @@ -3,7 +3,7 @@ * * Name: acpredef - Information table for ACPI predefined methods and objects * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/acresrc.h b/drivers/acpi/acpica/acresrc.h index 37c47e185fd4..f7749c63d277 100644 --- a/drivers/acpi/acpica/acresrc.h +++ b/drivers/acpi/acpica/acresrc.h @@ -3,7 +3,7 @@ * * Name: acresrc.h - Resource Manager function prototypes * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/acstruct.h b/drivers/acpi/acpica/acstruct.h index e3beb096c46d..b859de96a1e4 100644 --- a/drivers/acpi/acpica/acstruct.h +++ b/drivers/acpi/acpica/acstruct.h @@ -3,7 +3,7 @@ * * Name: acstruct.h - Internal structs * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/actables.h b/drivers/acpi/acpica/actables.h index 533802fe73e9..f8d7bfd737df 100644 --- a/drivers/acpi/acpica/actables.h +++ b/drivers/acpi/acpica/actables.h @@ -3,7 +3,7 @@ * * Name: actables.h - ACPI table management * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/acutils.h b/drivers/acpi/acpica/acutils.h index 59d6ded01614..6e6270f96bfb 100644 --- a/drivers/acpi/acpica/acutils.h +++ b/drivers/acpi/acpica/acutils.h @@ -3,7 +3,7 @@ * * Name: acutils.h -- prototypes for the common (subsystem-wide) procedures * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/amlcode.h b/drivers/acpi/acpica/amlcode.h index d6b088c5001f..62a7ec277513 100644 --- a/drivers/acpi/acpica/amlcode.h +++ b/drivers/acpi/acpica/amlcode.h @@ -5,7 +5,7 @@ * Declarations and definitions contained herein are derived * directly from the ACPI specification. * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/amlresrc.h b/drivers/acpi/acpica/amlresrc.h index b98123210281..b31779ce204a 100644 --- a/drivers/acpi/acpica/amlresrc.h +++ b/drivers/acpi/acpica/amlresrc.h @@ -3,7 +3,7 @@ * * Module Name: amlresrc.h - AML resource descriptors * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/dbhistry.c b/drivers/acpi/acpica/dbhistry.c index fd813c5d3952..105e6ceaa887 100644 --- a/drivers/acpi/acpica/dbhistry.c +++ b/drivers/acpi/acpica/dbhistry.c @@ -3,7 +3,7 @@ * * Module Name: dbhistry - debugger HISTORY command * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/dsargs.c b/drivers/acpi/acpica/dsargs.c index 6630d6536fb0..2963d1579c05 100644 --- a/drivers/acpi/acpica/dsargs.c +++ b/drivers/acpi/acpica/dsargs.c @@ -4,7 +4,7 @@ * Module Name: dsargs - Support for execution of dynamic arguments for static * objects (regions, fields, buffer fields, etc.) * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/dscontrol.c b/drivers/acpi/acpica/dscontrol.c index a152f03135cd..8492619149d1 100644 --- a/drivers/acpi/acpica/dscontrol.c +++ b/drivers/acpi/acpica/dscontrol.c @@ -4,7 +4,7 @@ * Module Name: dscontrol - Support for execution control opcodes - * if/else/while/return * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/dsdebug.c b/drivers/acpi/acpica/dsdebug.c index b9b03d629930..2d99ccf5bde7 100644 --- a/drivers/acpi/acpica/dsdebug.c +++ b/drivers/acpi/acpica/dsdebug.c @@ -3,7 +3,7 @@ * * Module Name: dsdebug - Parser/Interpreter interface - debugging * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/dsfield.c b/drivers/acpi/acpica/dsfield.c index a16817767969..de175f1b4beb 100644 --- a/drivers/acpi/acpica/dsfield.c +++ b/drivers/acpi/acpica/dsfield.c @@ -3,7 +3,7 @@ * * Module Name: dsfield - Dispatcher field routines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/dsinit.c b/drivers/acpi/acpica/dsinit.c index ba6f882e83bc..dffd54fdbd51 100644 --- a/drivers/acpi/acpica/dsinit.c +++ b/drivers/acpi/acpica/dsinit.c @@ -3,7 +3,7 @@ * * Module Name: dsinit - Object initialization namespace walk * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/dsmethod.c b/drivers/acpi/acpica/dsmethod.c index 8e011e59b9b4..ae2e768830bf 100644 --- a/drivers/acpi/acpica/dsmethod.c +++ b/drivers/acpi/acpica/dsmethod.c @@ -3,7 +3,7 @@ * * Module Name: dsmethod - Parser/Interpreter interface - control method parsing * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/dsobject.c b/drivers/acpi/acpica/dsobject.c index 3c0c31157e7e..e3dfc734ace9 100644 --- a/drivers/acpi/acpica/dsobject.c +++ b/drivers/acpi/acpica/dsobject.c @@ -3,7 +3,7 @@ * * Module Name: dsobject - Dispatcher object management routines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/dsopcode.c b/drivers/acpi/acpica/dsopcode.c index 44c448269861..2b9b6a974ca9 100644 --- a/drivers/acpi/acpica/dsopcode.c +++ b/drivers/acpi/acpica/dsopcode.c @@ -3,7 +3,7 @@ * * Module Name: dsopcode - Dispatcher support for regions and fields * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/dspkginit.c b/drivers/acpi/acpica/dspkginit.c index e642d65bcc66..1624d6e7dc46 100644 --- a/drivers/acpi/acpica/dspkginit.c +++ b/drivers/acpi/acpica/dspkginit.c @@ -3,7 +3,7 @@ * * Module Name: dspkginit - Completion of deferred package initialization * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/dswexec.c b/drivers/acpi/acpica/dswexec.c index f2d2267054af..e8ad41387f84 100644 --- a/drivers/acpi/acpica/dswexec.c +++ b/drivers/acpi/acpica/dswexec.c @@ -4,7 +4,7 @@ * Module Name: dswexec - Dispatcher method execution callbacks; * dispatch to interpreter. * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ @@ -30,7 +30,7 @@ static acpi_execute_op acpi_gbl_op_type_dispatch[] = { acpi_ex_opcode_0A_0T_1R, acpi_ex_opcode_1A_0T_0R, acpi_ex_opcode_1A_0T_1R, - acpi_ex_opcode_1A_1T_0R, + NULL, /* Was: acpi_ex_opcode_1A_0T_0R (Was for Load operator) */ acpi_ex_opcode_1A_1T_1R, acpi_ex_opcode_2A_0T_0R, acpi_ex_opcode_2A_0T_1R, diff --git a/drivers/acpi/acpica/dswload.c b/drivers/acpi/acpica/dswload.c index a377638e44f9..9f6573646ab5 100644 --- a/drivers/acpi/acpica/dswload.c +++ b/drivers/acpi/acpica/dswload.c @@ -3,7 +3,7 @@ * * Module Name: dswload - Dispatcher first pass namespace load callbacks * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/dswload2.c b/drivers/acpi/acpica/dswload2.c index 3625952c3957..778df616aaa0 100644 --- a/drivers/acpi/acpica/dswload2.c +++ b/drivers/acpi/acpica/dswload2.c @@ -3,7 +3,7 @@ * * Module Name: dswload2 - Dispatcher second pass namespace load callbacks * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/dswscope.c b/drivers/acpi/acpica/dswscope.c index 9c123af08bc1..634b9100f674 100644 --- a/drivers/acpi/acpica/dswscope.c +++ b/drivers/acpi/acpica/dswscope.c @@ -3,7 +3,7 @@ * * Module Name: dswscope - Scope stack manipulation * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/dswstate.c b/drivers/acpi/acpica/dswstate.c index fbe2ba05c82a..0aa735d3b93c 100644 --- a/drivers/acpi/acpica/dswstate.c +++ b/drivers/acpi/acpica/dswstate.c @@ -3,7 +3,7 @@ * * Module Name: dswstate - Dispatcher parse tree walk management routines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/evevent.c b/drivers/acpi/acpica/evevent.c index 35385148fedb..df596d46dd97 100644 --- a/drivers/acpi/acpica/evevent.c +++ b/drivers/acpi/acpica/evevent.c @@ -3,7 +3,7 @@ * * Module Name: evevent - Fixed Event handling and dispatch * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/evglock.c b/drivers/acpi/acpica/evglock.c index de4eea606ccd..9aab54797ded 100644 --- a/drivers/acpi/acpica/evglock.c +++ b/drivers/acpi/acpica/evglock.c @@ -3,7 +3,7 @@ * * Module Name: evglock - Global Lock support * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/evgpe.c b/drivers/acpi/acpica/evgpe.c index c5a06882bdf6..a6bb480d631c 100644 --- a/drivers/acpi/acpica/evgpe.c +++ b/drivers/acpi/acpica/evgpe.c @@ -3,7 +3,7 @@ * * Module Name: evgpe - General Purpose Event handling and dispatch * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/evgpeblk.c b/drivers/acpi/acpica/evgpeblk.c index e5f8245c2d93..39fe4566310b 100644 --- a/drivers/acpi/acpica/evgpeblk.c +++ b/drivers/acpi/acpica/evgpeblk.c @@ -3,7 +3,7 @@ * * Module Name: evgpeblk - GPE block creation and initialization. * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/evgpeinit.c b/drivers/acpi/acpica/evgpeinit.c index b0724d6e6e80..2f1a75fee61c 100644 --- a/drivers/acpi/acpica/evgpeinit.c +++ b/drivers/acpi/acpica/evgpeinit.c @@ -3,7 +3,7 @@ * * Module Name: evgpeinit - System GPE initialization and update * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/evgpeutil.c b/drivers/acpi/acpica/evgpeutil.c index 2e74308d7725..c32eb57aa21d 100644 --- a/drivers/acpi/acpica/evgpeutil.c +++ b/drivers/acpi/acpica/evgpeutil.c @@ -3,7 +3,7 @@ * * Module Name: evgpeutil - GPE utilities * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/evhandler.c b/drivers/acpi/acpica/evhandler.c index 8f43d38dc4ca..be9a05498adc 100644 --- a/drivers/acpi/acpica/evhandler.c +++ b/drivers/acpi/acpica/evhandler.c @@ -3,7 +3,7 @@ * * Module Name: evhandler - Support for Address Space handlers * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/evmisc.c b/drivers/acpi/acpica/evmisc.c index f14ebcd610ab..6172cddc1b39 100644 --- a/drivers/acpi/acpica/evmisc.c +++ b/drivers/acpi/acpica/evmisc.c @@ -3,7 +3,7 @@ * * Module Name: evmisc - Miscellaneous event manager support functions * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/evregion.c b/drivers/acpi/acpica/evregion.c index b9d77d327d38..b96b3a7e78e5 100644 --- a/drivers/acpi/acpica/evregion.c +++ b/drivers/acpi/acpica/evregion.c @@ -3,7 +3,7 @@ * * Module Name: evregion - Operation Region support * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/evrgnini.c b/drivers/acpi/acpica/evrgnini.c index d28dee929e61..ca4ba6b351fe 100644 --- a/drivers/acpi/acpica/evrgnini.c +++ b/drivers/acpi/acpica/evrgnini.c @@ -3,7 +3,7 @@ * * Module Name: evrgnini- ACPI address_space (op_region) init * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/evxface.c b/drivers/acpi/acpica/evxface.c index ff5cf5b0705a..18219abba108 100644 --- a/drivers/acpi/acpica/evxface.c +++ b/drivers/acpi/acpica/evxface.c @@ -3,7 +3,7 @@ * * Module Name: evxface - External interfaces for ACPI events * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/evxfevnt.c b/drivers/acpi/acpica/evxfevnt.c index 5445a361c621..8187b081e0a6 100644 --- a/drivers/acpi/acpica/evxfevnt.c +++ b/drivers/acpi/acpica/evxfevnt.c @@ -3,7 +3,7 @@ * * Module Name: evxfevnt - External Interfaces, ACPI event disable/enable * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/evxfgpe.c b/drivers/acpi/acpica/evxfgpe.c index a6d53cf86450..340947e412bb 100644 --- a/drivers/acpi/acpica/evxfgpe.c +++ b/drivers/acpi/acpica/evxfgpe.c @@ -3,7 +3,7 @@ * * Module Name: evxfgpe - External Interfaces for General Purpose Events (GPEs) * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/evxfregn.c b/drivers/acpi/acpica/evxfregn.c index b1ff0a8f9c14..0a8372bf6a77 100644 --- a/drivers/acpi/acpica/evxfregn.c +++ b/drivers/acpi/acpica/evxfregn.c @@ -4,7 +4,7 @@ * Module Name: evxfregn - External Interfaces, ACPI Operation Regions and * Address Spaces. * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exconcat.c b/drivers/acpi/acpica/exconcat.c index 2d220d470c60..66201742f499 100644 --- a/drivers/acpi/acpica/exconcat.c +++ b/drivers/acpi/acpica/exconcat.c @@ -3,7 +3,7 @@ * * Module Name: exconcat - Concatenate-type AML operators * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exconfig.c b/drivers/acpi/acpica/exconfig.c index 6c2685a6a4c1..d7d74ef87b18 100644 --- a/drivers/acpi/acpica/exconfig.c +++ b/drivers/acpi/acpica/exconfig.c @@ -3,7 +3,7 @@ * * Module Name: exconfig - Namespace reconfiguration (Load/Unload opcodes) * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ @@ -87,11 +87,21 @@ acpi_ex_load_table_op(struct acpi_walk_state *walk_state, struct acpi_namespace_node *parent_node; struct acpi_namespace_node *start_node; struct acpi_namespace_node *parameter_node = NULL; + union acpi_operand_object *return_obj; union acpi_operand_object *ddb_handle; u32 table_index; ACPI_FUNCTION_TRACE(ex_load_table_op); + /* Create the return object */ + + return_obj = acpi_ut_create_integer_object((u64)0); + if (!return_obj) { + return_ACPI_STATUS(AE_NO_MEMORY); + } + + *return_desc = return_obj; + /* Find the ACPI table in the RSDT/XSDT */ acpi_ex_exit_interpreter(); @@ -106,12 +116,6 @@ acpi_ex_load_table_op(struct acpi_walk_state *walk_state, /* Table not found, return an Integer=0 and AE_OK */ - ddb_handle = acpi_ut_create_integer_object((u64) 0); - if (!ddb_handle) { - return_ACPI_STATUS(AE_NO_MEMORY); - } - - *return_desc = ddb_handle; return_ACPI_STATUS(AE_OK); } @@ -198,7 +202,13 @@ acpi_ex_load_table_op(struct acpi_walk_state *walk_state, } } - *return_desc = ddb_handle; + /* Remove the reference to ddb_handle created by acpi_ex_add_table above */ + + acpi_ut_remove_reference(ddb_handle); + + /* Return -1 (non-zero) indicates success */ + + return_obj->integer.value = 0xFFFFFFFFFFFFFFFF; return_ACPI_STATUS(status); } @@ -249,7 +259,7 @@ acpi_ex_region_read(union acpi_operand_object *obj_desc, u32 length, u8 *buffer) * * PARAMETERS: obj_desc - Region or Buffer/Field where the table will be * obtained - * target - Where a handle to the table will be stored + * target - Where the status of the load will be stored * walk_state - Current state * * RETURN: Status @@ -278,6 +288,20 @@ acpi_ex_load_op(union acpi_operand_object *obj_desc, ACPI_FUNCTION_TRACE(ex_load_op); + if (target->common.descriptor_type == ACPI_DESC_TYPE_NAMED) { + target = + acpi_ns_get_attached_object(ACPI_CAST_PTR + (struct acpi_namespace_node, + target)); + } + if (target->common.type != ACPI_TYPE_INTEGER) { + ACPI_EXCEPTION((AE_INFO, AE_TYPE, + "Type not integer: %X\n", target->common.type)); + return_ACPI_STATUS(AE_AML_OPERAND_TYPE); + } + + target->integer.value = 0; + /* Source Object can be either an op_region or a Buffer/Field */ switch (obj_desc->common.type) { @@ -430,9 +454,6 @@ acpi_ex_load_op(union acpi_operand_object *obj_desc, */ status = acpi_ex_add_table(table_index, &ddb_handle); if (ACPI_FAILURE(status)) { - - /* On error, table_ptr was deallocated above */ - return_ACPI_STATUS(status); } @@ -442,21 +463,13 @@ acpi_ex_load_op(union acpi_operand_object *obj_desc, acpi_ns_initialize_objects(); acpi_ex_enter_interpreter(); - /* Store the ddb_handle into the Target operand */ - - status = acpi_ex_store(ddb_handle, target, walk_state); - if (ACPI_FAILURE(status)) { - (void)acpi_ex_unload_table(ddb_handle); - - /* table_ptr was deallocated above */ - - acpi_ut_remove_reference(ddb_handle); - return_ACPI_STATUS(status); - } - - /* Remove the reference by added by acpi_ex_store above */ + /* Remove the reference to ddb_handle created by acpi_ex_add_table above */ acpi_ut_remove_reference(ddb_handle); + + /* Return -1 (non-zero) indicates success */ + + target->integer.value = 0xFFFFFFFFFFFFFFFF; return_ACPI_STATUS(status); } diff --git a/drivers/acpi/acpica/exconvrt.c b/drivers/acpi/acpica/exconvrt.c index 6b7498371eb0..8de5d47ad485 100644 --- a/drivers/acpi/acpica/exconvrt.c +++ b/drivers/acpi/acpica/exconvrt.c @@ -3,7 +3,7 @@ * * Module Name: exconvrt - Object conversion routines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/excreate.c b/drivers/acpi/acpica/excreate.c index deb3674ae726..fb2453fa9442 100644 --- a/drivers/acpi/acpica/excreate.c +++ b/drivers/acpi/acpica/excreate.c @@ -3,7 +3,7 @@ * * Module Name: excreate - Named object creation * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exdebug.c b/drivers/acpi/acpica/exdebug.c index 6a01e38b7d5a..8a99aadb9d15 100644 --- a/drivers/acpi/acpica/exdebug.c +++ b/drivers/acpi/acpica/exdebug.c @@ -3,7 +3,7 @@ * * Module Name: exdebug - Support for stores to the AML Debug Object * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exdump.c b/drivers/acpi/acpica/exdump.c index 2aea44ecc37d..24b3d041b3e5 100644 --- a/drivers/acpi/acpica/exdump.c +++ b/drivers/acpi/acpica/exdump.c @@ -3,7 +3,7 @@ * * Module Name: exdump - Interpreter debug output routines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exfield.c b/drivers/acpi/acpica/exfield.c index 8618500f23b3..2b89a496de65 100644 --- a/drivers/acpi/acpica/exfield.c +++ b/drivers/acpi/acpica/exfield.c @@ -3,7 +3,7 @@ * * Module Name: exfield - AML execution - field_unit read/write * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exfldio.c b/drivers/acpi/acpica/exfldio.c index bdc7a30d1217..d769cea1468b 100644 --- a/drivers/acpi/acpica/exfldio.c +++ b/drivers/acpi/acpica/exfldio.c @@ -3,7 +3,7 @@ * * Module Name: exfldio - Aml Field I/O * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ @@ -104,7 +104,7 @@ acpi_ex_setup_region(union acpi_operand_object *obj_desc, #ifdef ACPI_UNDER_DEVELOPMENT /* * If the Field access is any_acc, we can now compute the optimal - * access (because we know know the length of the parent region) + * access (because we know the length of the parent region) */ if (!(obj_desc->common.flags & AOPOBJ_DATA_VALID)) { if (ACPI_FAILURE(status)) { diff --git a/drivers/acpi/acpica/exmisc.c b/drivers/acpi/acpica/exmisc.c index ad19f914641b..b4bac8c60a13 100644 --- a/drivers/acpi/acpica/exmisc.c +++ b/drivers/acpi/acpica/exmisc.c @@ -3,7 +3,7 @@ * * Module Name: exmisc - ACPI AML (p-code) execution - specific opcodes * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exmutex.c b/drivers/acpi/acpica/exmutex.c index 6237ae8284b1..e9dcfa1e93eb 100644 --- a/drivers/acpi/acpica/exmutex.c +++ b/drivers/acpi/acpica/exmutex.c @@ -3,7 +3,7 @@ * * Module Name: exmutex - ASL Mutex Acquire/Release functions * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exnames.c b/drivers/acpi/acpica/exnames.c index 5283603d078d..318eb769058d 100644 --- a/drivers/acpi/acpica/exnames.c +++ b/drivers/acpi/acpica/exnames.c @@ -3,7 +3,7 @@ * * Module Name: exnames - interpreter/scanner name load/execute * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exoparg1.c b/drivers/acpi/acpica/exoparg1.c index 44b7c350ed5c..d108a1a86f12 100644 --- a/drivers/acpi/acpica/exoparg1.c +++ b/drivers/acpi/acpica/exoparg1.c @@ -3,7 +3,7 @@ * * Module Name: exoparg1 - AML execution - opcodes with 1 argument * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ @@ -163,6 +163,7 @@ acpi_status acpi_ex_opcode_1A_0T_0R(struct acpi_walk_state *walk_state) return_ACPI_STATUS(status); } +#ifdef _OBSOLETE_CODE /* Was originally used for Load() operator */ /******************************************************************************* * * FUNCTION: acpi_ex_opcode_1A_1T_0R @@ -187,10 +188,12 @@ acpi_status acpi_ex_opcode_1A_1T_0R(struct acpi_walk_state *walk_state) /* Examine the AML opcode */ switch (walk_state->opcode) { +#ifdef _OBSOLETE_CODE case AML_LOAD_OP: status = acpi_ex_load_op(operand[0], operand[1], walk_state); break; +#endif default: /* Unknown opcode */ @@ -204,6 +207,7 @@ cleanup: return_ACPI_STATUS(status); } +#endif /******************************************************************************* * @@ -215,6 +219,8 @@ cleanup: * * DESCRIPTION: Execute opcode with one argument, one target, and a * return value. + * January 2022: Added Load operator, with new ACPI 6.4 + * semantics. * ******************************************************************************/ @@ -239,6 +245,7 @@ acpi_status acpi_ex_opcode_1A_1T_1R(struct acpi_walk_state *walk_state) case AML_FIND_SET_LEFT_BIT_OP: case AML_FIND_SET_RIGHT_BIT_OP: case AML_FROM_BCD_OP: + case AML_LOAD_OP: case AML_TO_BCD_OP: case AML_CONDITIONAL_REF_OF_OP: @@ -338,6 +345,20 @@ acpi_status acpi_ex_opcode_1A_1T_1R(struct acpi_walk_state *walk_state) } break; + case AML_LOAD_OP: /* Result1 = Load (Operand[0], Result1) */ + + return_desc->integer.value = 0; + status = + acpi_ex_load_op(operand[0], return_desc, + walk_state); + if (ACPI_SUCCESS(status)) { + + /* Return -1 (non-zero) indicates success */ + + return_desc->integer.value = 0xFFFFFFFFFFFFFFFF; + } + break; + case AML_TO_BCD_OP: /* to_bcd (Operand, Result) */ return_desc->integer.value = 0; diff --git a/drivers/acpi/acpica/exoparg2.c b/drivers/acpi/acpica/exoparg2.c index 10323ab186da..ebf7c89d52d9 100644 --- a/drivers/acpi/acpica/exoparg2.c +++ b/drivers/acpi/acpica/exoparg2.c @@ -3,7 +3,7 @@ * * Module Name: exoparg2 - AML execution - opcodes with 2 arguments * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exoparg3.c b/drivers/acpi/acpica/exoparg3.c index 140aae009690..4b069bd6bc71 100644 --- a/drivers/acpi/acpica/exoparg3.c +++ b/drivers/acpi/acpica/exoparg3.c @@ -3,7 +3,7 @@ * * Module Name: exoparg3 - AML execution - opcodes with 3 arguments * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exoparg6.c b/drivers/acpi/acpica/exoparg6.c index 2cf9f37a0ba8..2a506ef386cf 100644 --- a/drivers/acpi/acpica/exoparg6.c +++ b/drivers/acpi/acpica/exoparg6.c @@ -3,7 +3,7 @@ * * Module Name: exoparg6 - AML execution - opcodes with 6 arguments * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exprep.c b/drivers/acpi/acpica/exprep.c index d8c55dde191b..08f06797386a 100644 --- a/drivers/acpi/acpica/exprep.c +++ b/drivers/acpi/acpica/exprep.c @@ -3,7 +3,7 @@ * * Module Name: exprep - ACPI AML field prep utilities * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exregion.c b/drivers/acpi/acpica/exregion.c index 48c19908fa4e..4ff35852c0b3 100644 --- a/drivers/acpi/acpica/exregion.c +++ b/drivers/acpi/acpica/exregion.c @@ -3,7 +3,7 @@ * * Module Name: exregion - ACPI default op_region (address space) handlers * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exresnte.c b/drivers/acpi/acpica/exresnte.c index d80b76455c50..b81506d73447 100644 --- a/drivers/acpi/acpica/exresnte.c +++ b/drivers/acpi/acpica/exresnte.c @@ -3,7 +3,7 @@ * * Module Name: exresnte - AML Interpreter object resolution * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exresolv.c b/drivers/acpi/acpica/exresolv.c index fa6a96242835..61ee7fb46006 100644 --- a/drivers/acpi/acpica/exresolv.c +++ b/drivers/acpi/acpica/exresolv.c @@ -3,7 +3,7 @@ * * Module Name: exresolv - AML Interpreter object resolution * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exresop.c b/drivers/acpi/acpica/exresop.c index cbe2c88b1dc2..3342780230af 100644 --- a/drivers/acpi/acpica/exresop.c +++ b/drivers/acpi/acpica/exresop.c @@ -3,7 +3,7 @@ * * Module Name: exresop - AML Interpreter operand/object resolution * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exserial.c b/drivers/acpi/acpica/exserial.c index 10d68a5f76a3..4da20d7845df 100644 --- a/drivers/acpi/acpica/exserial.c +++ b/drivers/acpi/acpica/exserial.c @@ -3,7 +3,7 @@ * * Module Name: exserial - field_unit support for serial address spaces * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exstore.c b/drivers/acpi/acpica/exstore.c index 12f4210ea085..f99e8cf27a6c 100644 --- a/drivers/acpi/acpica/exstore.c +++ b/drivers/acpi/acpica/exstore.c @@ -3,7 +3,7 @@ * * Module Name: exstore - AML Interpreter object store support * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exstoren.c b/drivers/acpi/acpica/exstoren.c index 08469d37e73e..c848b328e760 100644 --- a/drivers/acpi/acpica/exstoren.c +++ b/drivers/acpi/acpica/exstoren.c @@ -4,7 +4,7 @@ * Module Name: exstoren - AML Interpreter object store support, * Store to Node (namespace object) * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exstorob.c b/drivers/acpi/acpica/exstorob.c index a82628683329..45c757bbf9a9 100644 --- a/drivers/acpi/acpica/exstorob.c +++ b/drivers/acpi/acpica/exstorob.c @@ -3,7 +3,7 @@ * * Module Name: exstorob - AML object store support, store to object * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exsystem.c b/drivers/acpi/acpica/exsystem.c index 1281c07112de..7b5470f404f3 100644 --- a/drivers/acpi/acpica/exsystem.c +++ b/drivers/acpi/acpica/exsystem.c @@ -3,7 +3,7 @@ * * Module Name: exsystem - Interface to OS services * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ @@ -107,7 +107,7 @@ acpi_status acpi_ex_system_wait_mutex(acpi_mutex mutex, u16 timeout) * * FUNCTION: acpi_ex_system_do_stall * - * PARAMETERS: how_long - The amount of time to stall, + * PARAMETERS: how_long_us - The amount of time to stall, * in microseconds * * RETURN: Status @@ -120,24 +120,29 @@ acpi_status acpi_ex_system_wait_mutex(acpi_mutex mutex, u16 timeout) * ******************************************************************************/ -acpi_status acpi_ex_system_do_stall(u32 how_long) +acpi_status acpi_ex_system_do_stall(u32 how_long_us) { acpi_status status = AE_OK; ACPI_FUNCTION_ENTRY(); - if (how_long > 255) { /* 255 microseconds */ + if (how_long_us > 255) { /* - * Longer than 255 usec, this is an error + * Longer than 255 microseconds, this is an error * * (ACPI specifies 100 usec as max, but this gives some slack in * order to support existing BIOSs) */ ACPI_ERROR((AE_INFO, - "Time parameter is too large (%u)", how_long)); + "Time parameter is too large (%u)", how_long_us)); status = AE_AML_OPERAND_VALUE; } else { - acpi_os_stall(how_long); + if (how_long_us > 100) { + ACPI_WARNING((AE_INFO, + "Time parameter %u us > 100 us violating ACPI spec, please fix the firmware.", + how_long_us)); + } + acpi_os_stall(how_long_us); } return (status); @@ -147,7 +152,7 @@ acpi_status acpi_ex_system_do_stall(u32 how_long) * * FUNCTION: acpi_ex_system_do_sleep * - * PARAMETERS: how_long - The amount of time to sleep, + * PARAMETERS: how_long_ms - The amount of time to sleep, * in milliseconds * * RETURN: None @@ -156,7 +161,7 @@ acpi_status acpi_ex_system_do_stall(u32 how_long) * ******************************************************************************/ -acpi_status acpi_ex_system_do_sleep(u64 how_long) +acpi_status acpi_ex_system_do_sleep(u64 how_long_ms) { ACPI_FUNCTION_ENTRY(); @@ -168,11 +173,11 @@ acpi_status acpi_ex_system_do_sleep(u64 how_long) * For compatibility with other ACPI implementations and to prevent * accidental deep sleeps, limit the sleep time to something reasonable. */ - if (how_long > ACPI_MAX_SLEEP) { - how_long = ACPI_MAX_SLEEP; + if (how_long_ms > ACPI_MAX_SLEEP) { + how_long_ms = ACPI_MAX_SLEEP; } - acpi_os_sleep(how_long); + acpi_os_sleep(how_long_ms); /* And now we must get the interpreter again */ diff --git a/drivers/acpi/acpica/extrace.c b/drivers/acpi/acpica/extrace.c index 8846f483fb02..b570d7a7e134 100644 --- a/drivers/acpi/acpica/extrace.c +++ b/drivers/acpi/acpica/extrace.c @@ -3,7 +3,7 @@ * * Module Name: extrace - Support for interpreter execution tracing * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/exutils.c b/drivers/acpi/acpica/exutils.c index 4d41a866f633..87f01ce1c1aa 100644 --- a/drivers/acpi/acpica/exutils.c +++ b/drivers/acpi/acpica/exutils.c @@ -3,7 +3,7 @@ * * Module Name: exutils - interpreter/scanner utilities * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/hwacpi.c b/drivers/acpi/acpica/hwacpi.c index 96f55f079988..2f1c2fc8bd2a 100644 --- a/drivers/acpi/acpica/hwacpi.c +++ b/drivers/acpi/acpica/hwacpi.c @@ -3,7 +3,7 @@ * * Module Name: hwacpi - ACPI Hardware Initialization/Mode Interface * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/hwesleep.c b/drivers/acpi/acpica/hwesleep.c index 7ee2939c08cd..d8597e052912 100644 --- a/drivers/acpi/acpica/hwesleep.c +++ b/drivers/acpi/acpica/hwesleep.c @@ -4,7 +4,7 @@ * Name: hwesleep.c - ACPI Hardware Sleep/Wake Support functions for the * extended FADT-V5 sleep registers. * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/hwgpe.c b/drivers/acpi/acpica/hwgpe.c index 0770aa176cd5..13d54a48e6e9 100644 --- a/drivers/acpi/acpica/hwgpe.c +++ b/drivers/acpi/acpica/hwgpe.c @@ -3,7 +3,7 @@ * * Module Name: hwgpe - Low level GPE enable/disable/clear functions * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/hwregs.c b/drivers/acpi/acpica/hwregs.c index 69603ba52a3a..f62d5d024205 100644 --- a/drivers/acpi/acpica/hwregs.c +++ b/drivers/acpi/acpica/hwregs.c @@ -446,7 +446,7 @@ struct acpi_bit_register_info *acpi_hw_get_bit_register_info(u32 register_id) * RETURN: Status * * DESCRIPTION: Write the PM1 A/B control registers. These registers are - * different than than the PM1 A/B status and enable registers + * different than the PM1 A/B status and enable registers * in that different values can be written to the A/B registers. * Most notably, the SLP_TYP bits can be different, as per the * values returned from the _Sx predefined methods. diff --git a/drivers/acpi/acpica/hwsleep.c b/drivers/acpi/acpica/hwsleep.c index 5efa3d8e483e..bd936476dda9 100644 --- a/drivers/acpi/acpica/hwsleep.c +++ b/drivers/acpi/acpica/hwsleep.c @@ -4,7 +4,7 @@ * Name: hwsleep.c - ACPI Hardware Sleep/Wake Support functions for the * original/legacy sleep/PM registers. * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/hwtimer.c b/drivers/acpi/acpica/hwtimer.c index 63deadde9f48..46f3ae03ab99 100644 --- a/drivers/acpi/acpica/hwtimer.c +++ b/drivers/acpi/acpica/hwtimer.c @@ -3,7 +3,7 @@ * * Name: hwtimer.c - ACPI Power Management Timer Interface * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/hwvalid.c b/drivers/acpi/acpica/hwvalid.c index e15badf4077a..915b26448d2c 100644 --- a/drivers/acpi/acpica/hwvalid.c +++ b/drivers/acpi/acpica/hwvalid.c @@ -3,7 +3,7 @@ * * Module Name: hwvalid - I/O request validation * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/hwxface.c b/drivers/acpi/acpica/hwxface.c index fb27aaad0dee..55d9b897e70f 100644 --- a/drivers/acpi/acpica/hwxface.c +++ b/drivers/acpi/acpica/hwxface.c @@ -3,7 +3,7 @@ * * Module Name: hwxface - Public ACPICA hardware interfaces * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/hwxfsleep.c b/drivers/acpi/acpica/hwxfsleep.c index ba77598ee43e..aff51ceea02c 100644 --- a/drivers/acpi/acpica/hwxfsleep.c +++ b/drivers/acpi/acpica/hwxfsleep.c @@ -3,7 +3,7 @@ * * Name: hwxfsleep.c - ACPI Hardware Sleep/Wake External Interfaces * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/nsarguments.c b/drivers/acpi/acpica/nsarguments.c index c8a2747005c5..22586b90e532 100644 --- a/drivers/acpi/acpica/nsarguments.c +++ b/drivers/acpi/acpica/nsarguments.c @@ -3,7 +3,7 @@ * * Module Name: nsarguments - Validation of args for ACPI predefined methods * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/nsconvert.c b/drivers/acpi/acpica/nsconvert.c index 597d0eed23c1..b02555fe38f0 100644 --- a/drivers/acpi/acpica/nsconvert.c +++ b/drivers/acpi/acpica/nsconvert.c @@ -4,7 +4,7 @@ * Module Name: nsconvert - Object conversions for objects returned by * predefined methods * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/nsdump.c b/drivers/acpi/acpica/nsdump.c index 2f66f3ed1810..f154824d4eb6 100644 --- a/drivers/acpi/acpica/nsdump.c +++ b/drivers/acpi/acpica/nsdump.c @@ -3,7 +3,7 @@ * * Module Name: nsdump - table dumping routines for debug * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/nsdumpdv.c b/drivers/acpi/acpica/nsdumpdv.c index d3dc6761bcdd..b9a88b7b518b 100644 --- a/drivers/acpi/acpica/nsdumpdv.c +++ b/drivers/acpi/acpica/nsdumpdv.c @@ -3,7 +3,7 @@ * * Module Name: nsdump - table dumping routines for debug * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/nsinit.c b/drivers/acpi/acpica/nsinit.c index 4db81f8ba29b..3e6207ad18d8 100644 --- a/drivers/acpi/acpica/nsinit.c +++ b/drivers/acpi/acpica/nsinit.c @@ -3,7 +3,7 @@ * * Module Name: nsinit - namespace initialization * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/nsload.c b/drivers/acpi/acpica/nsload.c index 7d77956ed790..880260a30c0c 100644 --- a/drivers/acpi/acpica/nsload.c +++ b/drivers/acpi/acpica/nsload.c @@ -3,7 +3,7 @@ * * Module Name: nsload - namespace loading/expanding/contracting procedures * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/nsparse.c b/drivers/acpi/acpica/nsparse.c index 778f80e624be..4b893676ab5c 100644 --- a/drivers/acpi/acpica/nsparse.c +++ b/drivers/acpi/acpica/nsparse.c @@ -3,7 +3,7 @@ * * Module Name: nsparse - namespace interface to AML parser * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/nspredef.c b/drivers/acpi/acpica/nspredef.c index e4e5f32da7dc..c0db6690bb32 100644 --- a/drivers/acpi/acpica/nspredef.c +++ b/drivers/acpi/acpica/nspredef.c @@ -3,7 +3,7 @@ * * Module Name: nspredef - Validation of ACPI predefined methods and objects * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/nsprepkg.c b/drivers/acpi/acpica/nsprepkg.c index 6742b50836f7..82932c9a774b 100644 --- a/drivers/acpi/acpica/nsprepkg.c +++ b/drivers/acpi/acpica/nsprepkg.c @@ -3,7 +3,7 @@ * * Module Name: nsprepkg - Validation of package objects for predefined names * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/nsrepair.c b/drivers/acpi/acpica/nsrepair.c index 499067daa22c..367fcd201f96 100644 --- a/drivers/acpi/acpica/nsrepair.c +++ b/drivers/acpi/acpica/nsrepair.c @@ -3,7 +3,7 @@ * * Module Name: nsrepair - Repair for objects returned by predefined methods * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/nsrepair2.c b/drivers/acpi/acpica/nsrepair2.c index 14b71b41e845..dd533c887e3a 100644 --- a/drivers/acpi/acpica/nsrepair2.c +++ b/drivers/acpi/acpica/nsrepair2.c @@ -4,7 +4,7 @@ * Module Name: nsrepair2 - Repair for objects returned by specific * predefined methods * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/nsutils.c b/drivers/acpi/acpica/nsutils.c index 83d0f276da4d..ef531b145add 100644 --- a/drivers/acpi/acpica/nsutils.c +++ b/drivers/acpi/acpica/nsutils.c @@ -4,7 +4,7 @@ * Module Name: nsutils - Utilities for accessing ACPI namespace, accessing * parents and siblings and Scope manipulation * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/nswalk.c b/drivers/acpi/acpica/nswalk.c index e7c30ce06e18..82a0dae349e2 100644 --- a/drivers/acpi/acpica/nswalk.c +++ b/drivers/acpi/acpica/nswalk.c @@ -3,7 +3,7 @@ * * Module Name: nswalk - Functions for walking the ACPI namespace * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/nsxfname.c b/drivers/acpi/acpica/nsxfname.c index 03487546da5a..b2cfdfef3194 100644 --- a/drivers/acpi/acpica/nsxfname.c +++ b/drivers/acpi/acpica/nsxfname.c @@ -4,7 +4,7 @@ * Module Name: nsxfname - Public interfaces to the ACPI subsystem * ACPI Namespace oriented interfaces * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/psargs.c b/drivers/acpi/acpica/psargs.c index b9ff535aa02e..f7ec5606098c 100644 --- a/drivers/acpi/acpica/psargs.c +++ b/drivers/acpi/acpica/psargs.c @@ -3,7 +3,7 @@ * * Module Name: psargs - Parse AML opcode arguments * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/psloop.c b/drivers/acpi/acpica/psloop.c index 4b51dd939f29..840512fa9fc6 100644 --- a/drivers/acpi/acpica/psloop.c +++ b/drivers/acpi/acpica/psloop.c @@ -3,7 +3,7 @@ * * Module Name: psloop - Main AML parse loop * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/psobject.c b/drivers/acpi/acpica/psobject.c index e4420cd6d281..bca249e67c6b 100644 --- a/drivers/acpi/acpica/psobject.c +++ b/drivers/acpi/acpica/psobject.c @@ -3,7 +3,7 @@ * * Module Name: psobject - Support for parse objects * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/psopcode.c b/drivers/acpi/acpica/psopcode.c index 3e80eb1a5f35..bef69e87a0a2 100644 --- a/drivers/acpi/acpica/psopcode.c +++ b/drivers/acpi/acpica/psopcode.c @@ -3,7 +3,7 @@ * * Module Name: psopcode - Parser/Interpreter opcode information table * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ @@ -408,8 +408,8 @@ const struct acpi_opcode_info acpi_gbl_aml_op_info[AML_NUM_OPCODES] = { AML_HAS_ARGS | AML_NSOBJECT | AML_NSNODE | AML_DEFER | AML_FIELD | AML_CREATE), /* 4A */ ACPI_OP("Load", ARGP_LOAD_OP, ARGI_LOAD_OP, ACPI_TYPE_ANY, - AML_CLASS_EXECUTE, AML_TYPE_EXEC_1A_1T_0R, - AML_FLAGS_EXEC_1A_1T_0R), + AML_CLASS_EXECUTE, AML_TYPE_EXEC_1A_1T_1R, + AML_FLAGS_EXEC_1A_1T_1R), /* 4B */ ACPI_OP("Stall", ARGP_STALL_OP, ARGI_STALL_OP, ACPI_TYPE_ANY, AML_CLASS_EXECUTE, AML_TYPE_EXEC_1A_0T_0R, AML_FLAGS_EXEC_1A_0T_0R), diff --git a/drivers/acpi/acpica/psopinfo.c b/drivers/acpi/acpica/psopinfo.c index 476b00a121f3..f10afe699ad7 100644 --- a/drivers/acpi/acpica/psopinfo.c +++ b/drivers/acpi/acpica/psopinfo.c @@ -3,7 +3,7 @@ * * Module Name: psopinfo - AML opcode information functions and dispatch tables * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/psparse.c b/drivers/acpi/acpica/psparse.c index 7eb7a81619a3..ba93f359760a 100644 --- a/drivers/acpi/acpica/psparse.c +++ b/drivers/acpi/acpica/psparse.c @@ -3,7 +3,7 @@ * * Module Name: psparse - Parser top level AML parse routines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/psscope.c b/drivers/acpi/acpica/psscope.c index 3f2eada44942..400f001631ea 100644 --- a/drivers/acpi/acpica/psscope.c +++ b/drivers/acpi/acpica/psscope.c @@ -3,7 +3,7 @@ * * Module Name: psscope - Parser scope stack management routines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/pstree.c b/drivers/acpi/acpica/pstree.c index ffb2a7bfc6d7..3012a9342367 100644 --- a/drivers/acpi/acpica/pstree.c +++ b/drivers/acpi/acpica/pstree.c @@ -3,7 +3,7 @@ * * Module Name: pstree - Parser op tree manipulation/traversal/search * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/psutils.c b/drivers/acpi/acpica/psutils.c index e6596051d548..49b39aeded12 100644 --- a/drivers/acpi/acpica/psutils.c +++ b/drivers/acpi/acpica/psutils.c @@ -3,7 +3,7 @@ * * Module Name: psutils - Parser miscellaneous utilities (Parser only) * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/pswalk.c b/drivers/acpi/acpica/pswalk.c index 7018a789debc..7735a01dab90 100644 --- a/drivers/acpi/acpica/pswalk.c +++ b/drivers/acpi/acpica/pswalk.c @@ -3,7 +3,7 @@ * * Module Name: pswalk - Parser routines to walk parsed op tree(s) * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/psxface.c b/drivers/acpi/acpica/psxface.c index fd0f28c7af1e..a6509aeb2955 100644 --- a/drivers/acpi/acpica/psxface.c +++ b/drivers/acpi/acpica/psxface.c @@ -3,7 +3,7 @@ * * Module Name: psxface - Parser external interfaces * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/tbdata.c b/drivers/acpi/acpica/tbdata.c index 20360a9db482..a7642b34ce48 100644 --- a/drivers/acpi/acpica/tbdata.c +++ b/drivers/acpi/acpica/tbdata.c @@ -3,7 +3,7 @@ * * Module Name: tbdata - Table manager data structure functions * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/tbfadt.c b/drivers/acpi/acpica/tbfadt.c index 047bd094ba68..31d7ea84a360 100644 --- a/drivers/acpi/acpica/tbfadt.c +++ b/drivers/acpi/acpica/tbfadt.c @@ -3,7 +3,7 @@ * * Module Name: tbfadt - FADT table utilities * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/tbfind.c b/drivers/acpi/acpica/tbfind.c index 2c2c2b1f5a28..c31a5ddb0ffd 100644 --- a/drivers/acpi/acpica/tbfind.c +++ b/drivers/acpi/acpica/tbfind.c @@ -3,7 +3,7 @@ * * Module Name: tbfind - find table * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/tbinstal.c b/drivers/acpi/acpica/tbinstal.c index 5649f493a1ed..499efcaf798d 100644 --- a/drivers/acpi/acpica/tbinstal.c +++ b/drivers/acpi/acpica/tbinstal.c @@ -3,7 +3,7 @@ * * Module Name: tbinstal - ACPI table installation and removal * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/tbprint.c b/drivers/acpi/acpica/tbprint.c index 4dac16bd63d3..595547db28c0 100644 --- a/drivers/acpi/acpica/tbprint.c +++ b/drivers/acpi/acpica/tbprint.c @@ -3,7 +3,7 @@ * * Module Name: tbprint - Table output utilities * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/tbutils.c b/drivers/acpi/acpica/tbutils.c index 5e8d50a4b6a9..633a823be65f 100644 --- a/drivers/acpi/acpica/tbutils.c +++ b/drivers/acpi/acpica/tbutils.c @@ -3,7 +3,7 @@ * * Module Name: tbutils - ACPI Table utilities * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/tbxface.c b/drivers/acpi/acpica/tbxface.c index e6f51fedaf1a..37da09dca940 100644 --- a/drivers/acpi/acpica/tbxface.c +++ b/drivers/acpi/acpica/tbxface.c @@ -3,7 +3,7 @@ * * Module Name: tbxface - ACPI table-oriented external interfaces * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/tbxfload.c b/drivers/acpi/acpica/tbxfload.c index 87356d9ad613..258796e02be1 100644 --- a/drivers/acpi/acpica/tbxfload.c +++ b/drivers/acpi/acpica/tbxfload.c @@ -3,7 +3,7 @@ * * Module Name: tbxfload - Table load/unload external interfaces * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/tbxfroot.c b/drivers/acpi/acpica/tbxfroot.c index 9fec3df6c3ba..3d09e3f6bd43 100644 --- a/drivers/acpi/acpica/tbxfroot.c +++ b/drivers/acpi/acpica/tbxfroot.c @@ -3,7 +3,7 @@ * * Module Name: tbxfroot - Find the root ACPI table (RSDT) * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utaddress.c b/drivers/acpi/acpica/utaddress.c index 7001f4b113f1..915321806cd7 100644 --- a/drivers/acpi/acpica/utaddress.c +++ b/drivers/acpi/acpica/utaddress.c @@ -3,7 +3,7 @@ * * Module Name: utaddress - op_region address range check * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utalloc.c b/drivers/acpi/acpica/utalloc.c index 796fd9b33a7d..2bab6017d827 100644 --- a/drivers/acpi/acpica/utalloc.c +++ b/drivers/acpi/acpica/utalloc.c @@ -3,7 +3,7 @@ * * Module Name: utalloc - local memory allocation routines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utascii.c b/drivers/acpi/acpica/utascii.c index e1b55575d5fb..72fb7e9ec485 100644 --- a/drivers/acpi/acpica/utascii.c +++ b/drivers/acpi/acpica/utascii.c @@ -3,7 +3,7 @@ * * Module Name: utascii - Utility ascii functions * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utbuffer.c b/drivers/acpi/acpica/utbuffer.c index 8ab90f78825b..59c4050b8e91 100644 --- a/drivers/acpi/acpica/utbuffer.c +++ b/drivers/acpi/acpica/utbuffer.c @@ -3,7 +3,7 @@ * * Module Name: utbuffer - Buffer dump routines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utcache.c b/drivers/acpi/acpica/utcache.c index 814145019f95..5425968dd2a8 100644 --- a/drivers/acpi/acpica/utcache.c +++ b/drivers/acpi/acpica/utcache.c @@ -3,7 +3,7 @@ * * Module Name: utcache - local cache allocation routines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utcopy.c b/drivers/acpi/acpica/utcopy.c index d9877153f400..400b9e15a709 100644 --- a/drivers/acpi/acpica/utcopy.c +++ b/drivers/acpi/acpica/utcopy.c @@ -3,7 +3,7 @@ * * Module Name: utcopy - Internal to external object translation utilities * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utdebug.c b/drivers/acpi/acpica/utdebug.c index 09245945f319..64ed546cf19c 100644 --- a/drivers/acpi/acpica/utdebug.c +++ b/drivers/acpi/acpica/utdebug.c @@ -3,7 +3,7 @@ * * Module Name: utdebug - Debug print/trace routines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utdecode.c b/drivers/acpi/acpica/utdecode.c index bcd3871079d7..3176393a729d 100644 --- a/drivers/acpi/acpica/utdecode.c +++ b/drivers/acpi/acpica/utdecode.c @@ -3,7 +3,7 @@ * * Module Name: utdecode - Utility decoding routines (value-to-string) * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/uteval.c b/drivers/acpi/acpica/uteval.c index d2503920c620..df20d46ed8b7 100644 --- a/drivers/acpi/acpica/uteval.c +++ b/drivers/acpi/acpica/uteval.c @@ -3,7 +3,7 @@ * * Module Name: uteval - Object evaluation * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utglobal.c b/drivers/acpi/acpica/utglobal.c index 59a48371a7bc..cda6e16dddf7 100644 --- a/drivers/acpi/acpica/utglobal.c +++ b/drivers/acpi/acpica/utglobal.c @@ -3,7 +3,7 @@ * * Module Name: utglobal - Global variables for the ACPI subsystem * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/uthex.c b/drivers/acpi/acpica/uthex.c index b1e94c094f9a..c811ee2a8160 100644 --- a/drivers/acpi/acpica/uthex.c +++ b/drivers/acpi/acpica/uthex.c @@ -3,7 +3,7 @@ * * Module Name: uthex -- Hex/ASCII support functions * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utids.c b/drivers/acpi/acpica/utids.c index 08e9f316cbde..b6caab49f1bd 100644 --- a/drivers/acpi/acpica/utids.c +++ b/drivers/acpi/acpica/utids.c @@ -3,7 +3,7 @@ * * Module Name: utids - support for device Ids - HID, UID, CID, SUB, CLS * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utinit.c b/drivers/acpi/acpica/utinit.c index 7b606a1e6986..18177e4f26f7 100644 --- a/drivers/acpi/acpica/utinit.c +++ b/drivers/acpi/acpica/utinit.c @@ -3,7 +3,7 @@ * * Module Name: utinit - Common ACPI subsystem initialization * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utlock.c b/drivers/acpi/acpica/utlock.c index 923dd15e7a16..84abdbf5cfca 100644 --- a/drivers/acpi/acpica/utlock.c +++ b/drivers/acpi/acpica/utlock.c @@ -3,7 +3,7 @@ * * Module Name: utlock - Reader/Writer lock interfaces * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utobject.c b/drivers/acpi/acpica/utobject.c index 84a210b49e3a..d3667bfff401 100644 --- a/drivers/acpi/acpica/utobject.c +++ b/drivers/acpi/acpica/utobject.c @@ -3,7 +3,7 @@ * * Module Name: utobject - ACPI object create/delete/size/cache routines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utosi.c b/drivers/acpi/acpica/utosi.c index 8afa1ccaf12e..b8ab0a3cb5b9 100644 --- a/drivers/acpi/acpica/utosi.c +++ b/drivers/acpi/acpica/utosi.c @@ -3,7 +3,7 @@ * * Module Name: utosi - Support for the _OSI predefined control method * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ @@ -74,6 +74,7 @@ static struct acpi_interface_info acpi_default_supported_interfaces[] = { {"Windows 2018.2", NULL, 0, ACPI_OSI_WIN_10_RS5}, /* Windows 10 version 1809 - Added 11/2018 */ {"Windows 2019", NULL, 0, ACPI_OSI_WIN_10_19H1}, /* Windows 10 version 1903 - Added 08/2019 */ {"Windows 2020", NULL, 0, ACPI_OSI_WIN_10_20H1}, /* Windows 10 version 2004 - Added 08/2021 */ + {"Windows 2021", NULL, 0, ACPI_OSI_WIN_11}, /* Windows 11 - Added 01/2022 */ /* Feature Group Strings */ diff --git a/drivers/acpi/acpica/utpredef.c b/drivers/acpi/acpica/utpredef.c index a6f87a88c30e..2524f013be7a 100644 --- a/drivers/acpi/acpica/utpredef.c +++ b/drivers/acpi/acpica/utpredef.c @@ -3,7 +3,7 @@ * * Module Name: utpredef - support functions for predefined names * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utprint.c b/drivers/acpi/acpica/utprint.c index 05426596d1f4..d5aa2109847f 100644 --- a/drivers/acpi/acpica/utprint.c +++ b/drivers/acpi/acpica/utprint.c @@ -3,7 +3,7 @@ * * Module Name: utprint - Formatted printing routines * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/uttrack.c b/drivers/acpi/acpica/uttrack.c index 2ce85fcfeb5b..a06988ac409d 100644 --- a/drivers/acpi/acpica/uttrack.c +++ b/drivers/acpi/acpica/uttrack.c @@ -3,7 +3,7 @@ * * Module Name: uttrack - Memory allocation tracking routines (debug only) * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utuuid.c b/drivers/acpi/acpica/utuuid.c index dca9061518ab..e24bc670b53e 100644 --- a/drivers/acpi/acpica/utuuid.c +++ b/drivers/acpi/acpica/utuuid.c @@ -3,7 +3,7 @@ * * Module Name: utuuid -- UUID support functions * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utxface.c b/drivers/acpi/acpica/utxface.c index 3285c1a92e40..86e76b443da7 100644 --- a/drivers/acpi/acpica/utxface.c +++ b/drivers/acpi/acpica/utxface.c @@ -3,7 +3,7 @@ * * Module Name: utxface - External interfaces, miscellaneous utility functions * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/acpica/utxfinit.c b/drivers/acpi/acpica/utxfinit.c index 91016366de1d..f2acec3a0ee3 100644 --- a/drivers/acpi/acpica/utxfinit.c +++ b/drivers/acpi/acpica/utxfinit.c @@ -3,7 +3,7 @@ * * Module Name: utxfinit - External interfaces for ACPICA initialization * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/drivers/acpi/apei/einj.c b/drivers/acpi/apei/einj.c index 95cc2a9f3e05..d4326ec12d29 100644 --- a/drivers/acpi/apei/einj.c +++ b/drivers/acpi/apei/einj.c @@ -549,6 +549,9 @@ static int einj_error_inject(u32 type, u32 flags, u64 param1, u64 param2, !arch_is_platform_page(base_addr))) return -EINVAL; + if (is_zero_pfn(base_addr >> PAGE_SHIFT)) + return -EADDRINUSE; + inject: mutex_lock(&einj_mutex); rc = __einj_error_inject(type, flags, param1, param2, param3, param4); diff --git a/drivers/acpi/apei/erst-dbg.c b/drivers/acpi/apei/erst-dbg.c index c740f0faad39..8bc71cdc2270 100644 --- a/drivers/acpi/apei/erst-dbg.c +++ b/drivers/acpi/apei/erst-dbg.c @@ -111,7 +111,8 @@ retry_next: goto out; } retry: - rc = len = erst_read(id, erst_dbg_buf, erst_dbg_buf_len); + rc = len = erst_read_record(id, erst_dbg_buf, erst_dbg_buf_len, + erst_dbg_buf_len, NULL); /* The record may be cleared by others, try read next record */ if (rc == -ENOENT) goto retry_next; diff --git a/drivers/acpi/apei/erst.c b/drivers/acpi/apei/erst.c index 698d67cee052..31b077eedb58 100644 --- a/drivers/acpi/apei/erst.c +++ b/drivers/acpi/apei/erst.c @@ -856,6 +856,74 @@ ssize_t erst_read(u64 record_id, struct cper_record_header *record, } EXPORT_SYMBOL_GPL(erst_read); +static void erst_clear_cache(u64 record_id) +{ + int i; + u64 *entries; + + mutex_lock(&erst_record_id_cache.lock); + + entries = erst_record_id_cache.entries; + for (i = 0; i < erst_record_id_cache.len; i++) { + if (entries[i] == record_id) + entries[i] = APEI_ERST_INVALID_RECORD_ID; + } + __erst_record_id_cache_compact(); + + mutex_unlock(&erst_record_id_cache.lock); +} + +ssize_t erst_read_record(u64 record_id, struct cper_record_header *record, + size_t buflen, size_t recordlen, const guid_t *creatorid) +{ + ssize_t len; + + /* + * if creatorid is NULL, read any record for erst-dbg module + */ + if (creatorid == NULL) { + len = erst_read(record_id, record, buflen); + if (len == -ENOENT) + erst_clear_cache(record_id); + + return len; + } + + len = erst_read(record_id, record, buflen); + /* + * if erst_read return value is -ENOENT skip to next record_id, + * and clear the record_id cache. + */ + if (len == -ENOENT) { + erst_clear_cache(record_id); + goto out; + } + + if (len < 0) + goto out; + + /* + * if erst_read return value is less than record head length, + * consider it as -EIO, and clear the record_id cache. + */ + if (len < recordlen) { + len = -EIO; + erst_clear_cache(record_id); + goto out; + } + + /* + * if creatorid is not wanted, consider it as not found, + * for skipping to next record_id. + */ + if (!guid_equal(&record->creator_id, creatorid)) + len = -ENOENT; + +out: + return len; +} +EXPORT_SYMBOL_GPL(erst_read_record); + int erst_clear(u64 record_id) { int rc, i; @@ -996,16 +1064,13 @@ skip: goto out; } - len = erst_read(record_id, &rcd->hdr, rcd_len); + len = erst_read_record(record_id, &rcd->hdr, rcd_len, sizeof(*rcd), + &CPER_CREATOR_PSTORE); /* The record may be cleared by others, try read next record */ if (len == -ENOENT) goto skip; - else if (len < 0 || len < sizeof(*rcd)) { - rc = -EIO; + else if (len < 0) goto out; - } - if (!guid_equal(&rcd->hdr.creator_id, &CPER_CREATOR_PSTORE)) - goto skip; record->buf = kmalloc(len, GFP_KERNEL); if (record->buf == NULL) { diff --git a/drivers/acpi/arm64/agdi.c b/drivers/acpi/arm64/agdi.c index 4df337d545b7..cf31abd0ed1b 100644 --- a/drivers/acpi/arm64/agdi.c +++ b/drivers/acpi/arm64/agdi.c @@ -9,6 +9,7 @@ #define pr_fmt(fmt) "ACPI: AGDI: " fmt #include +#include #include #include #include diff --git a/drivers/acpi/bgrt.c b/drivers/acpi/bgrt.c index 02d208732f9a..e4fb9e225ddf 100644 --- a/drivers/acpi/bgrt.c +++ b/drivers/acpi/bgrt.c @@ -21,7 +21,7 @@ static struct kobject *bgrt_kobj; { \ return sysfs_emit(buf, "%d\n", bgrt_tab._member); \ } \ - struct kobj_attribute bgrt_attr_##_name = __ATTR_RO(_name) + static struct kobj_attribute bgrt_attr_##_name = __ATTR_RO(_name) BGRT_SHOW(version, version); BGRT_SHOW(status, status); diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index 3e58b613a2c4..b67d2ee77cd1 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -278,6 +278,20 @@ bool osc_sb_apei_support_acked; bool osc_pc_lpi_support_confirmed; EXPORT_SYMBOL_GPL(osc_pc_lpi_support_confirmed); +/* + * ACPI 6.2 Section 6.2.11.2 'Platform-Wide OSPM Capabilities': + * Starting with ACPI Specification 6.2, all _CPC registers can be in + * PCC, System Memory, System IO, or Functional Fixed Hardware address + * spaces. OSPM support for this more flexible register space scheme is + * indicated by the “Flexible Address Space for CPPC Registers” _OSC bit. + * + * Otherwise (cf ACPI 6.1, s8.4.7.1.1.X), _CPC registers must be in: + * - PCC or Functional Fixed Hardware address space if defined + * - SystemMemory address space (NULL register) if not defined + */ +bool osc_cpc_flexible_adr_space_confirmed; +EXPORT_SYMBOL_GPL(osc_cpc_flexible_adr_space_confirmed); + /* * ACPI 6.4 Operating System Capabilities for USB. */ @@ -315,12 +329,15 @@ static void acpi_bus_osc_negotiate_platform_control(void) #endif #ifdef CONFIG_X86 capbuf[OSC_SUPPORT_DWORD] |= OSC_SB_GENERIC_INITIATOR_SUPPORT; - if (boot_cpu_has(X86_FEATURE_HWP)) { - capbuf[OSC_SUPPORT_DWORD] |= OSC_SB_CPC_SUPPORT; - capbuf[OSC_SUPPORT_DWORD] |= OSC_SB_CPCV2_SUPPORT; - } #endif +#ifdef CONFIG_ACPI_CPPC_LIB + capbuf[OSC_SUPPORT_DWORD] |= OSC_SB_CPC_SUPPORT; + capbuf[OSC_SUPPORT_DWORD] |= OSC_SB_CPCV2_SUPPORT; +#endif + + capbuf[OSC_SUPPORT_DWORD] |= OSC_SB_CPC_FLEXIBLE_ADR_SPACE; + if (IS_ENABLED(CONFIG_SCHED_MC_PRIO)) capbuf[OSC_SUPPORT_DWORD] |= OSC_SB_CPC_DIVERSE_HIGH_SUPPORT; @@ -341,10 +358,9 @@ static void acpi_bus_osc_negotiate_platform_control(void) return; } -#ifdef CONFIG_X86 - if (boot_cpu_has(X86_FEATURE_HWP)) - osc_sb_cppc_not_supported = !(capbuf_ret[OSC_SUPPORT_DWORD] & - (OSC_SB_CPC_SUPPORT | OSC_SB_CPCV2_SUPPORT)); +#ifdef CONFIG_ACPI_CPPC_LIB + osc_sb_cppc_not_supported = !(capbuf_ret[OSC_SUPPORT_DWORD] & + (OSC_SB_CPC_SUPPORT | OSC_SB_CPCV2_SUPPORT)); #endif /* @@ -366,6 +382,8 @@ static void acpi_bus_osc_negotiate_platform_control(void) capbuf_ret[OSC_SUPPORT_DWORD] & OSC_SB_PCLPI_SUPPORT; osc_sb_native_usb4_support_confirmed = capbuf_ret[OSC_SUPPORT_DWORD] & OSC_SB_NATIVE_USB4_SUPPORT; + osc_cpc_flexible_adr_space_confirmed = + capbuf_ret[OSC_SUPPORT_DWORD] & OSC_SB_CPC_FLEXIBLE_ADR_SPACE; } kfree(context.ret.pointer); @@ -1070,6 +1088,32 @@ int acpi_bus_for_each_dev(int (*fn)(struct device *, void *), void *data) } EXPORT_SYMBOL_GPL(acpi_bus_for_each_dev); +struct acpi_dev_walk_context { + int (*fn)(struct acpi_device *, void *); + void *data; +}; + +static int acpi_dev_for_one_check(struct device *dev, void *context) +{ + struct acpi_dev_walk_context *adwc = context; + + if (dev->bus != &acpi_bus_type) + return 0; + + return adwc->fn(to_acpi_device(dev), adwc->data); +} + +int acpi_dev_for_each_child(struct acpi_device *adev, + int (*fn)(struct acpi_device *, void *), void *data) +{ + struct acpi_dev_walk_context adwc = { + .fn = fn, + .data = data, + }; + + return device_for_each_child(&adev->dev, &adwc, acpi_dev_for_one_check); +} + /* -------------------------------------------------------------------------- Initialization/Cleanup -------------------------------------------------------------------------- */ diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index bc1454789a06..3b299b28a8af 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -100,6 +100,16 @@ static DEFINE_PER_CPU(struct cpc_desc *, cpc_desc_ptr); (cpc)->cpc_entry.reg.space_id == \ ACPI_ADR_SPACE_PLATFORM_COMM) +/* Check if a CPC register is in SystemMemory */ +#define CPC_IN_SYSTEM_MEMORY(cpc) ((cpc)->type == ACPI_TYPE_BUFFER && \ + (cpc)->cpc_entry.reg.space_id == \ + ACPI_ADR_SPACE_SYSTEM_MEMORY) + +/* Check if a CPC register is in SystemIo */ +#define CPC_IN_SYSTEM_IO(cpc) ((cpc)->type == ACPI_TYPE_BUFFER && \ + (cpc)->cpc_entry.reg.space_id == \ + ACPI_ADR_SPACE_SYSTEM_IO) + /* Evaluates to True if reg is a NULL register descriptor */ #define IS_NULL_REG(reg) ((reg)->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY && \ (reg)->address == 0 && \ @@ -424,6 +434,24 @@ bool acpi_cpc_valid(void) } EXPORT_SYMBOL_GPL(acpi_cpc_valid); +bool cppc_allow_fast_switch(void) +{ + struct cpc_register_resource *desired_reg; + struct cpc_desc *cpc_ptr; + int cpu; + + for_each_possible_cpu(cpu) { + cpc_ptr = per_cpu(cpc_desc_ptr, cpu); + desired_reg = &cpc_ptr->cpc_regs[DESIRED_PERF]; + if (!CPC_IN_SYSTEM_MEMORY(desired_reg) && + !CPC_IN_SYSTEM_IO(desired_reg)) + return false; + } + + return true; +} +EXPORT_SYMBOL_GPL(cppc_allow_fast_switch); + /** * acpi_get_psd_map - Map the CPUs in the freq domain of a given cpu * @cpu: Find all CPUs that share a domain with cpu. @@ -736,6 +764,11 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) if (gas_t->address) { void __iomem *addr; + if (!osc_cpc_flexible_adr_space_confirmed) { + pr_debug("Flexible address space capability not supported\n"); + goto out_free; + } + addr = ioremap(gas_t->address, gas_t->bit_width/8); if (!addr) goto out_free; @@ -758,6 +791,10 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) gas_t->address); goto out_free; } + if (!osc_cpc_flexible_adr_space_confirmed) { + pr_debug("Flexible address space capability not supported\n"); + goto out_free; + } } else { if (gas_t->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE || !cpc_ffh_supported()) { /* Support only PCC, SystemMemory, SystemIO, and FFH type regs. */ @@ -1447,6 +1484,9 @@ EXPORT_SYMBOL_GPL(cppc_set_perf); * transition latency for performance change requests. The closest we have * is the timing information from the PCCT tables which provides the info * on the number and frequency of PCC commands the platform can handle. + * + * If desired_reg is in the SystemMemory or SystemIo ACPI address space, + * then assume there is no latency. */ unsigned int cppc_get_transition_latency(int cpu_num) { @@ -1472,7 +1512,9 @@ unsigned int cppc_get_transition_latency(int cpu_num) return CPUFREQ_ETERNAL; desired_reg = &cpc_desc->cpc_regs[DESIRED_PERF]; - if (!CPC_IN_PCC(desired_reg)) + if (CPC_IN_SYSTEM_MEMORY(desired_reg) || CPC_IN_SYSTEM_IO(desired_reg)) + return 0; + else if (!CPC_IN_PCC(desired_reg)) return CPUFREQ_ETERNAL; if (pcc_ss_id < 0) diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c index cc6c97e7dcae..130b5f4a50a3 100644 --- a/drivers/acpi/device_pm.c +++ b/drivers/acpi/device_pm.c @@ -10,7 +10,7 @@ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -#define pr_fmt(fmt) "ACPI: PM: " fmt +#define pr_fmt(fmt) "PM: " fmt #include #include @@ -130,8 +130,8 @@ int acpi_device_get_power(struct acpi_device *device, int *state) *state = result; out: - dev_dbg(&device->dev, "Device power state is %s\n", - acpi_power_state_string(*state)); + acpi_handle_debug(device->handle, "Power state: %s\n", + acpi_power_state_string(*state)); return 0; } @@ -173,11 +173,8 @@ int acpi_device_set_power(struct acpi_device *device, int state) /* Make sure this is a valid target state */ /* There is a special case for D0 addressed below. */ - if (state > ACPI_STATE_D0 && state == device->power.state) { - dev_dbg(&device->dev, "Device already in %s\n", - acpi_power_state_string(state)); - return 0; - } + if (state > ACPI_STATE_D0 && state == device->power.state) + goto no_change; if (state == ACPI_STATE_D3_COLD) { /* @@ -189,17 +186,17 @@ int acpi_device_set_power(struct acpi_device *device, int state) if (!device->power.states[ACPI_STATE_D3_COLD].flags.valid) target_state = state; } else if (!device->power.states[state].flags.valid) { - dev_warn(&device->dev, "Power state %s not supported\n", - acpi_power_state_string(state)); + acpi_handle_debug(device->handle, "Power state %s not supported\n", + acpi_power_state_string(state)); return -ENODEV; } - if (!device->power.flags.ignore_parent && - device->parent && (state < device->parent->power.state)) { - dev_warn(&device->dev, - "Cannot transition to power state %s for parent in %s\n", - acpi_power_state_string(state), - acpi_power_state_string(device->parent->power.state)); + if (!device->power.flags.ignore_parent && device->parent && + state < device->parent->power.state) { + acpi_handle_debug(device->handle, + "Cannot transition to %s for parent in %s\n", + acpi_power_state_string(state), + acpi_power_state_string(device->parent->power.state)); return -ENODEV; } @@ -216,9 +213,10 @@ int acpi_device_set_power(struct acpi_device *device, int state) * (deeper) states to higher-power (shallower) states. */ if (state < device->power.state) { - dev_warn(&device->dev, "Cannot transition from %s to %s\n", - acpi_power_state_string(device->power.state), - acpi_power_state_string(state)); + acpi_handle_debug(device->handle, + "Cannot transition from %s to %s\n", + acpi_power_state_string(device->power.state), + acpi_power_state_string(state)); return -ENODEV; } @@ -248,7 +246,7 @@ int acpi_device_set_power(struct acpi_device *device, int state) /* Nothing to do here if _PSC is not present. */ if (!device->power.flags.explicit_get) - return 0; + goto no_change; /* * The power state of the device was set to D0 last @@ -263,23 +261,29 @@ int acpi_device_set_power(struct acpi_device *device, int state) */ result = acpi_dev_pm_explicit_get(device, &psc); if (result || psc == ACPI_STATE_D0) - return 0; + goto no_change; } result = acpi_dev_pm_explicit_set(device, ACPI_STATE_D0); } - end: +end: if (result) { - dev_warn(&device->dev, "Failed to change power state to %s\n", - acpi_power_state_string(target_state)); + acpi_handle_debug(device->handle, + "Failed to change power state to %s\n", + acpi_power_state_string(target_state)); } else { device->power.state = target_state; - dev_dbg(&device->dev, "Power state changed to %s\n", - acpi_power_state_string(target_state)); + acpi_handle_debug(device->handle, "Power state changed to %s\n", + acpi_power_state_string(target_state)); } return result; + +no_change: + acpi_handle_debug(device->handle, "Already in %s\n", + acpi_power_state_string(state)); + return 0; } EXPORT_SYMBOL(acpi_device_set_power); @@ -425,6 +429,33 @@ bool acpi_bus_power_manageable(acpi_handle handle) } EXPORT_SYMBOL(acpi_bus_power_manageable); +static int acpi_power_up_if_adr_present(struct acpi_device *adev, void *not_used) +{ + if (!(adev->flags.power_manageable && adev->pnp.type.bus_address)) + return 0; + + acpi_handle_debug(adev->handle, "Power state: %s\n", + acpi_power_state_string(adev->power.state)); + + if (adev->power.state == ACPI_STATE_D3_COLD) + return acpi_device_set_power(adev, ACPI_STATE_D0); + + return 0; +} + +/** + * acpi_dev_power_up_children_with_adr - Power up childres with valid _ADR + * @adev: Parent ACPI device object. + * + * Change the power states of the direct children of @adev that are in D3cold + * and hold valid _ADR objects to D0 in order to allow bus (e.g. PCI) + * enumeration code to access them. + */ +void acpi_dev_power_up_children_with_adr(struct acpi_device *adev) +{ + acpi_dev_for_each_child(adev, acpi_power_up_if_adr_present, NULL); +} + #ifdef CONFIG_PM static DEFINE_MUTEX(acpi_pm_notifier_lock); static DEFINE_MUTEX(acpi_pm_notifier_install_lock); diff --git a/drivers/acpi/dptf/dptf_power.c b/drivers/acpi/dptf/dptf_power.c index dc1f52a5b3f4..407b89d8a2ce 100644 --- a/drivers/acpi/dptf/dptf_power.c +++ b/drivers/acpi/dptf/dptf_power.c @@ -12,14 +12,12 @@ /* * Presentation of attributes which are defined for INT3407 and INT3532. * They are: - * PMAX : Maximum platform powe + * PMAX : Maximum platform power * PSRC : Platform power source * ARTG : Adapter rating * CTYP : Charger type - * PBSS : Battery steady power * PROP : Rest of worst case platform Power * PBSS : Power Battery Steady State - * PBSS : Power Battery Steady State * RBHF : High Frequency Impedance * VBNL : Instantaneous No-Load Voltage * CMPP : Current Discharge Capability @@ -117,7 +115,7 @@ static const struct attribute_group dptf_battery_attribute_group = { #define POWER_STATE_CHANGED 0x81 #define STEADY_STATE_POWER_CHANGED 0x83 #define POWER_PROP_CHANGE_EVENT 0x84 -#define IMPEDANCED_CHNGED 0x85 +#define IMPEDANCE_CHANGED 0x85 #define VOLTAGE_CURRENT_CHANGED 0x86 static long long dptf_participant_type(acpi_handle handle) @@ -150,6 +148,9 @@ static void dptf_power_notify(acpi_handle handle, u32 event, void *data) case STEADY_STATE_POWER_CHANGED: attr = "max_steady_state_power_mw"; break; + case IMPEDANCE_CHANGED: + attr = "high_freq_impedance_mohm"; + break; case VOLTAGE_CURRENT_CHANGED: attr = "no_load_voltage_mv"; break; diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 6f9e75d14808..b3b507f20e87 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -927,6 +927,8 @@ struct pci_bus *acpi_pci_root_create(struct acpi_pci_root *root, host_bridge->preserve_config = 1; ACPI_FREE(obj); + acpi_dev_power_up_children_with_adr(device); + pci_scan_child_bus(bus); pci_set_host_bridge_release(host_bridge, acpi_pci_root_release_info, info); diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index c992e57b2c79..3147702710af 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -373,6 +373,18 @@ static const struct dmi_system_id acpisleep_dmi_table[] __initconst = { DMI_MATCH(DMI_PRODUCT_NAME, "20GGA00L00"), }, }, + /* + * ASUS B1400CEAE hangs on resume from suspend (see + * https://bugzilla.kernel.org/show_bug.cgi?id=215742). + */ + { + .callback = init_default_s3, + .ident = "ASUS B1400CEAE", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "ASUS EXPERTBOOK B1400CEAE"), + }, + }, {}, }; diff --git a/drivers/acpi/spcr.c b/drivers/acpi/spcr.c index d589543875b8..1eabfcd122ee 100644 --- a/drivers/acpi/spcr.c +++ b/drivers/acpi/spcr.c @@ -142,6 +142,7 @@ int __init acpi_parse_spcr(bool enable_earlycon, bool enable_console) case ACPI_DBG2_16550_COMPATIBLE: case ACPI_DBG2_16550_SUBSET: case ACPI_DBG2_16550_WITH_GAS: + case ACPI_DBG2_16550_NVIDIA: uart = "uart"; break; default: diff --git a/drivers/acpi/sysfs.c b/drivers/acpi/sysfs.c index a4b638bea6f1..cc2fe0618178 100644 --- a/drivers/acpi/sysfs.c +++ b/drivers/acpi/sysfs.c @@ -415,19 +415,30 @@ static ssize_t acpi_data_show(struct file *filp, struct kobject *kobj, loff_t offset, size_t count) { struct acpi_data_attr *data_attr; - void *base; - ssize_t rc; + void __iomem *base; + ssize_t size; data_attr = container_of(bin_attr, struct acpi_data_attr, attr); + size = data_attr->attr.size; - base = acpi_os_map_memory(data_attr->addr, data_attr->attr.size); + if (offset < 0) + return -EINVAL; + + if (offset >= size) + return 0; + + if (count > size - offset) + count = size - offset; + + base = acpi_os_map_iomem(data_attr->addr, size); if (!base) return -ENOMEM; - rc = memory_read_from_buffer(buf, count, &offset, base, - data_attr->attr.size); - acpi_os_unmap_memory(base, data_attr->attr.size); - return rc; + memcpy_fromio(buf, base + offset, count); + + acpi_os_unmap_iomem(base, size); + + return count; } static int acpi_bert_data_init(void *th, struct acpi_data_attr *data_attr) diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c index d5cedffeeff9..3a9773a09e19 100644 --- a/drivers/acpi/utils.c +++ b/drivers/acpi/utils.c @@ -681,7 +681,7 @@ acpi_evaluate_dsm(acpi_handle handle, const guid_t *guid, u64 rev, u64 func, if (ret != AE_NOT_FOUND) acpi_handle_warn(handle, - "failed to evaluate _DSM (0x%x)\n", ret); + "failed to evaluate _DSM %pUb (0x%x)\n", guid, ret); return NULL; } diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c index bbddb267c2e6..72115917e0bd 100644 --- a/drivers/base/power/common.c +++ b/drivers/base/power/common.c @@ -172,10 +172,10 @@ EXPORT_SYMBOL_GPL(dev_pm_domain_attach_by_name); * @dev: Device to detach. * @power_off: Used to indicate whether we should power off the device. * - * This functions will reverse the actions from dev_pm_domain_attach() and - * dev_pm_domain_attach_by_id(), thus it detaches @dev from its PM domain. - * Typically it should be invoked during the remove phase, either from - * subsystem level code or from drivers. + * This functions will reverse the actions from dev_pm_domain_attach(), + * dev_pm_domain_attach_by_id() and dev_pm_domain_attach_by_name(), thus it + * detaches @dev from its PM domain. Typically it should be invoked during the + * remove phase, either from subsystem level code or from drivers. * * Callers must ensure proper synchronization of this function with power * management callbacks. diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 1ee878d126fd..739e52cd4aba 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -131,7 +131,7 @@ static const struct genpd_lock_ops genpd_spin_ops = { #define genpd_is_cpu_domain(genpd) (genpd->flags & GENPD_FLAG_CPU_DOMAIN) #define genpd_is_rpm_always_on(genpd) (genpd->flags & GENPD_FLAG_RPM_ALWAYS_ON) -static inline bool irq_safe_dev_in_no_sleep_domain(struct device *dev, +static inline bool irq_safe_dev_in_sleep_domain(struct device *dev, const struct generic_pm_domain *genpd) { bool ret; @@ -139,11 +139,14 @@ static inline bool irq_safe_dev_in_no_sleep_domain(struct device *dev, ret = pm_runtime_is_irq_safe(dev) && !genpd_is_irq_safe(genpd); /* - * Warn once if an IRQ safe device is attached to a no sleep domain, as - * to indicate a suboptimal configuration for PM. For an always on - * domain this isn't case, thus don't warn. + * Warn once if an IRQ safe device is attached to a domain, which + * callbacks are allowed to sleep. This indicates a suboptimal + * configuration for PM, but it doesn't matter for an always on domain. */ - if (ret && !genpd_is_always_on(genpd)) + if (genpd_is_always_on(genpd) || genpd_is_rpm_always_on(genpd)) + return ret; + + if (ret) dev_warn_once(dev, "PM domain %s will not be powered off\n", genpd->name); @@ -225,24 +228,23 @@ static void genpd_debug_remove(struct generic_pm_domain *genpd) static void genpd_update_accounting(struct generic_pm_domain *genpd) { - ktime_t delta, now; + u64 delta, now; - now = ktime_get(); - delta = ktime_sub(now, genpd->accounting_time); + now = ktime_get_mono_fast_ns(); + if (now <= genpd->accounting_time) + return; + + delta = now - genpd->accounting_time; /* * If genpd->status is active, it means we are just * out of off and so update the idle time and vice * versa. */ - if (genpd->status == GENPD_STATE_ON) { - int state_idx = genpd->state_idx; - - genpd->states[state_idx].idle_time = - ktime_add(genpd->states[state_idx].idle_time, delta); - } else { - genpd->on_time = ktime_add(genpd->on_time, delta); - } + if (genpd->status == GENPD_STATE_ON) + genpd->states[genpd->state_idx].idle_time += delta; + else + genpd->on_time += delta; genpd->accounting_time = now; } @@ -476,15 +478,16 @@ EXPORT_SYMBOL_GPL(dev_pm_genpd_set_performance_state); */ void dev_pm_genpd_set_next_wakeup(struct device *dev, ktime_t next) { - struct generic_pm_domain_data *gpd_data; struct generic_pm_domain *genpd; + struct gpd_timing_data *td; genpd = dev_to_genpd_safe(dev); if (!genpd) return; - gpd_data = to_gpd_data(dev->power.subsys_data->domain_data); - gpd_data->next_wakeup = next; + td = to_gpd_data(dev->power.subsys_data->domain_data)->td; + if (td) + td->next_wakeup = next; } EXPORT_SYMBOL_GPL(dev_pm_genpd_set_next_wakeup); @@ -506,6 +509,7 @@ static int _genpd_power_on(struct generic_pm_domain *genpd, bool timed) if (!genpd->power_on) goto out; + timed = timed && genpd->gd && !genpd->states[state_idx].fwnode; if (!timed) { ret = genpd->power_on(genpd); if (ret) @@ -524,7 +528,7 @@ static int _genpd_power_on(struct generic_pm_domain *genpd, bool timed) goto out; genpd->states[state_idx].power_on_latency_ns = elapsed_ns; - genpd->max_off_time_changed = true; + genpd->gd->max_off_time_changed = true; pr_debug("%s: Power-%s latency exceeded, new value %lld ns\n", genpd->name, "on", elapsed_ns); @@ -555,6 +559,7 @@ static int _genpd_power_off(struct generic_pm_domain *genpd, bool timed) if (!genpd->power_off) goto out; + timed = timed && genpd->gd && !genpd->states[state_idx].fwnode; if (!timed) { ret = genpd->power_off(genpd); if (ret) @@ -573,7 +578,7 @@ static int _genpd_power_off(struct generic_pm_domain *genpd, bool timed) goto out; genpd->states[state_idx].power_off_latency_ns = elapsed_ns; - genpd->max_off_time_changed = true; + genpd->gd->max_off_time_changed = true; pr_debug("%s: Power-%s latency exceeded, new value %lld ns\n", genpd->name, "off", elapsed_ns); @@ -649,18 +654,12 @@ static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on, } list_for_each_entry(pdd, &genpd->dev_list, list_node) { - enum pm_qos_flags_status stat; - - stat = dev_pm_qos_flags(pdd->dev, PM_QOS_FLAG_NO_POWER_OFF); - if (stat > PM_QOS_FLAGS_NONE) - return -EBUSY; - /* * Do not allow PM domain to be powered off, when an IRQ safe * device is part of a non-IRQ safe domain. */ if (!pm_runtime_suspended(pdd->dev) || - irq_safe_dev_in_no_sleep_domain(pdd->dev, genpd)) + irq_safe_dev_in_sleep_domain(pdd->dev, genpd)) not_suspended++; } @@ -775,25 +774,27 @@ static int genpd_dev_pm_qos_notifier(struct notifier_block *nb, dev = gpd_data->base.dev; for (;;) { - struct generic_pm_domain *genpd; + struct generic_pm_domain *genpd = ERR_PTR(-ENODATA); struct pm_domain_data *pdd; + struct gpd_timing_data *td; spin_lock_irq(&dev->power.lock); pdd = dev->power.subsys_data ? dev->power.subsys_data->domain_data : NULL; if (pdd) { - to_gpd_data(pdd)->td.constraint_changed = true; - genpd = dev_to_genpd(dev); - } else { - genpd = ERR_PTR(-ENODATA); + td = to_gpd_data(pdd)->td; + if (td) { + td->constraint_changed = true; + genpd = dev_to_genpd(dev); + } } spin_unlock_irq(&dev->power.lock); if (!IS_ERR(genpd)) { genpd_lock(genpd); - genpd->max_off_time_changed = true; + genpd->gd->max_off_time_changed = true; genpd_unlock(genpd); } @@ -879,9 +880,9 @@ static int genpd_runtime_suspend(struct device *dev) struct generic_pm_domain *genpd; bool (*suspend_ok)(struct device *__dev); struct generic_pm_domain_data *gpd_data = dev_gpd_data(dev); - struct gpd_timing_data *td = &gpd_data->td; + struct gpd_timing_data *td = gpd_data->td; bool runtime_pm = pm_runtime_enabled(dev); - ktime_t time_start; + ktime_t time_start = 0; s64 elapsed_ns; int ret; @@ -902,8 +903,7 @@ static int genpd_runtime_suspend(struct device *dev) return -EBUSY; /* Measure suspend latency. */ - time_start = 0; - if (runtime_pm) + if (td && runtime_pm) time_start = ktime_get(); ret = __genpd_runtime_suspend(dev); @@ -917,13 +917,13 @@ static int genpd_runtime_suspend(struct device *dev) } /* Update suspend latency value if the measured time exceeds it. */ - if (runtime_pm) { + if (td && runtime_pm) { elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start)); if (elapsed_ns > td->suspend_latency_ns) { td->suspend_latency_ns = elapsed_ns; dev_dbg(dev, "suspend latency exceeded, %lld ns\n", elapsed_ns); - genpd->max_off_time_changed = true; + genpd->gd->max_off_time_changed = true; td->constraint_changed = true; } } @@ -932,7 +932,7 @@ static int genpd_runtime_suspend(struct device *dev) * If power.irq_safe is set, this routine may be run with * IRQs disabled, so suspend only if the PM domain also is irq_safe. */ - if (irq_safe_dev_in_no_sleep_domain(dev, genpd)) + if (irq_safe_dev_in_sleep_domain(dev, genpd)) return 0; genpd_lock(genpd); @@ -955,12 +955,11 @@ static int genpd_runtime_resume(struct device *dev) { struct generic_pm_domain *genpd; struct generic_pm_domain_data *gpd_data = dev_gpd_data(dev); - struct gpd_timing_data *td = &gpd_data->td; - bool runtime_pm = pm_runtime_enabled(dev); - ktime_t time_start; + struct gpd_timing_data *td = gpd_data->td; + bool timed = td && pm_runtime_enabled(dev); + ktime_t time_start = 0; s64 elapsed_ns; int ret; - bool timed = true; dev_dbg(dev, "%s()\n", __func__); @@ -972,10 +971,8 @@ static int genpd_runtime_resume(struct device *dev) * As we don't power off a non IRQ safe domain, which holds * an IRQ safe device, we don't need to restore power to it. */ - if (irq_safe_dev_in_no_sleep_domain(dev, genpd)) { - timed = false; + if (irq_safe_dev_in_sleep_domain(dev, genpd)) goto out; - } genpd_lock(genpd); ret = genpd_power_on(genpd, 0); @@ -988,8 +985,7 @@ static int genpd_runtime_resume(struct device *dev) out: /* Measure resume latency. */ - time_start = 0; - if (timed && runtime_pm) + if (timed) time_start = ktime_get(); ret = genpd_start_dev(genpd, dev); @@ -1001,13 +997,13 @@ static int genpd_runtime_resume(struct device *dev) goto err_stop; /* Update resume latency value if the measured time exceeds it. */ - if (timed && runtime_pm) { + if (timed) { elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start)); if (elapsed_ns > td->resume_latency_ns) { td->resume_latency_ns = elapsed_ns; dev_dbg(dev, "resume latency exceeded, %lld ns\n", elapsed_ns); - genpd->max_off_time_changed = true; + genpd->gd->max_off_time_changed = true; td->constraint_changed = true; } } @@ -1500,9 +1496,11 @@ EXPORT_SYMBOL_GPL(dev_pm_genpd_resume); #endif /* CONFIG_PM_SLEEP */ -static struct generic_pm_domain_data *genpd_alloc_dev_data(struct device *dev) +static struct generic_pm_domain_data *genpd_alloc_dev_data(struct device *dev, + bool has_governor) { struct generic_pm_domain_data *gpd_data; + struct gpd_timing_data *td; int ret; ret = dev_pm_get_subsys_data(dev); @@ -1516,26 +1514,38 @@ static struct generic_pm_domain_data *genpd_alloc_dev_data(struct device *dev) } gpd_data->base.dev = dev; - gpd_data->td.constraint_changed = true; - gpd_data->td.effective_constraint_ns = PM_QOS_RESUME_LATENCY_NO_CONSTRAINT_NS; gpd_data->nb.notifier_call = genpd_dev_pm_qos_notifier; - gpd_data->next_wakeup = KTIME_MAX; + + /* Allocate data used by a governor. */ + if (has_governor) { + td = kzalloc(sizeof(*td), GFP_KERNEL); + if (!td) { + ret = -ENOMEM; + goto err_free; + } + + td->constraint_changed = true; + td->effective_constraint_ns = PM_QOS_RESUME_LATENCY_NO_CONSTRAINT_NS; + td->next_wakeup = KTIME_MAX; + gpd_data->td = td; + } spin_lock_irq(&dev->power.lock); - if (dev->power.subsys_data->domain_data) { + if (dev->power.subsys_data->domain_data) ret = -EINVAL; - goto err_free; - } - - dev->power.subsys_data->domain_data = &gpd_data->base; + else + dev->power.subsys_data->domain_data = &gpd_data->base; spin_unlock_irq(&dev->power.lock); + if (ret) + goto err_free; + return gpd_data; err_free: - spin_unlock_irq(&dev->power.lock); + kfree(gpd_data->td); kfree(gpd_data); err_put: dev_pm_put_subsys_data(dev); @@ -1551,6 +1561,7 @@ static void genpd_free_dev_data(struct device *dev, spin_unlock_irq(&dev->power.lock); + kfree(gpd_data->td); kfree(gpd_data); dev_pm_put_subsys_data(dev); } @@ -1607,6 +1618,7 @@ static int genpd_get_cpu(struct generic_pm_domain *genpd, struct device *dev) static int genpd_add_device(struct generic_pm_domain *genpd, struct device *dev, struct device *base_dev) { + struct genpd_governor_data *gd = genpd->gd; struct generic_pm_domain_data *gpd_data; int ret; @@ -1615,7 +1627,7 @@ static int genpd_add_device(struct generic_pm_domain *genpd, struct device *dev, if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(dev)) return -EINVAL; - gpd_data = genpd_alloc_dev_data(dev); + gpd_data = genpd_alloc_dev_data(dev, gd); if (IS_ERR(gpd_data)) return PTR_ERR(gpd_data); @@ -1631,7 +1643,8 @@ static int genpd_add_device(struct generic_pm_domain *genpd, struct device *dev, dev_pm_domain_set(dev, &genpd->domain); genpd->device_count++; - genpd->max_off_time_changed = true; + if (gd) + gd->max_off_time_changed = true; list_add_tail(&gpd_data->base.list_node, &genpd->dev_list); @@ -1685,7 +1698,8 @@ static int genpd_remove_device(struct generic_pm_domain *genpd, } genpd->device_count--; - genpd->max_off_time_changed = true; + if (genpd->gd) + genpd->gd->max_off_time_changed = true; genpd_clear_cpumask(genpd, gpd_data->cpu); dev_pm_domain_set(dev, NULL); @@ -1958,6 +1972,53 @@ static int genpd_set_default_power_state(struct generic_pm_domain *genpd) return 0; } +static int genpd_alloc_data(struct generic_pm_domain *genpd) +{ + struct genpd_governor_data *gd = NULL; + int ret; + + if (genpd_is_cpu_domain(genpd) && + !zalloc_cpumask_var(&genpd->cpus, GFP_KERNEL)) + return -ENOMEM; + + if (genpd->gov) { + gd = kzalloc(sizeof(*gd), GFP_KERNEL); + if (!gd) { + ret = -ENOMEM; + goto free; + } + + gd->max_off_time_ns = -1; + gd->max_off_time_changed = true; + gd->next_wakeup = KTIME_MAX; + } + + /* Use only one "off" state if there were no states declared */ + if (genpd->state_count == 0) { + ret = genpd_set_default_power_state(genpd); + if (ret) + goto free; + } + + genpd->gd = gd; + return 0; + +free: + if (genpd_is_cpu_domain(genpd)) + free_cpumask_var(genpd->cpus); + kfree(gd); + return ret; +} + +static void genpd_free_data(struct generic_pm_domain *genpd) +{ + if (genpd_is_cpu_domain(genpd)) + free_cpumask_var(genpd->cpus); + if (genpd->free_states) + genpd->free_states(genpd->states, genpd->state_count); + kfree(genpd->gd); +} + static void genpd_lock_init(struct generic_pm_domain *genpd) { if (genpd->flags & GENPD_FLAG_IRQ_SAFE) { @@ -1995,11 +2056,9 @@ int pm_genpd_init(struct generic_pm_domain *genpd, atomic_set(&genpd->sd_count, 0); genpd->status = is_off ? GENPD_STATE_OFF : GENPD_STATE_ON; genpd->device_count = 0; - genpd->max_off_time_ns = -1; - genpd->max_off_time_changed = true; genpd->provider = NULL; genpd->has_provider = false; - genpd->accounting_time = ktime_get(); + genpd->accounting_time = ktime_get_mono_fast_ns(); genpd->domain.ops.runtime_suspend = genpd_runtime_suspend; genpd->domain.ops.runtime_resume = genpd_runtime_resume; genpd->domain.ops.prepare = genpd_prepare; @@ -2017,26 +2076,22 @@ int pm_genpd_init(struct generic_pm_domain *genpd, genpd->dev_ops.start = pm_clk_resume; } + /* The always-on governor works better with the corresponding flag. */ + if (gov == &pm_domain_always_on_gov) + genpd->flags |= GENPD_FLAG_RPM_ALWAYS_ON; + /* Always-on domains must be powered on at initialization. */ if ((genpd_is_always_on(genpd) || genpd_is_rpm_always_on(genpd)) && !genpd_status_on(genpd)) return -EINVAL; - if (genpd_is_cpu_domain(genpd) && - !zalloc_cpumask_var(&genpd->cpus, GFP_KERNEL)) - return -ENOMEM; - - /* Use only one "off" state if there were no states declared */ - if (genpd->state_count == 0) { - ret = genpd_set_default_power_state(genpd); - if (ret) { - if (genpd_is_cpu_domain(genpd)) - free_cpumask_var(genpd->cpus); - return ret; - } - } else if (!gov && genpd->state_count > 1) { + /* Multiple states but no governor doesn't make sense. */ + if (!gov && genpd->state_count > 1) pr_warn("%s: no governor for states\n", genpd->name); - } + + ret = genpd_alloc_data(genpd); + if (ret) + return ret; device_initialize(&genpd->dev); dev_set_name(&genpd->dev, "%s", genpd->name); @@ -2081,10 +2136,7 @@ static int genpd_remove(struct generic_pm_domain *genpd) genpd_unlock(genpd); genpd_debug_remove(genpd); cancel_work_sync(&genpd->power_off_work); - if (genpd_is_cpu_domain(genpd)) - free_cpumask_var(genpd->cpus); - if (genpd->free_states) - genpd->free_states(genpd->states, genpd->state_count); + genpd_free_data(genpd); pr_debug("%s: removed %s\n", __func__, genpd->name); @@ -3163,6 +3215,7 @@ static int sub_domains_show(struct seq_file *s, void *data) static int idle_states_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd = s->private; + u64 now, delta, idle_time = 0; unsigned int i; int ret = 0; @@ -3173,17 +3226,19 @@ static int idle_states_show(struct seq_file *s, void *data) seq_puts(s, "State Time Spent(ms) Usage Rejected\n"); for (i = 0; i < genpd->state_count; i++) { - ktime_t delta = 0; - s64 msecs; + idle_time += genpd->states[i].idle_time; - if ((genpd->status == GENPD_STATE_OFF) && - (genpd->state_idx == i)) - delta = ktime_sub(ktime_get(), genpd->accounting_time); + if (genpd->status == GENPD_STATE_OFF && genpd->state_idx == i) { + now = ktime_get_mono_fast_ns(); + if (now > genpd->accounting_time) { + delta = now - genpd->accounting_time; + idle_time += delta; + } + } - msecs = ktime_to_ms( - ktime_add(genpd->states[i].idle_time, delta)); - seq_printf(s, "S%-13i %-14lld %-14llu %llu\n", i, msecs, - genpd->states[i].usage, genpd->states[i].rejected); + do_div(idle_time, NSEC_PER_MSEC); + seq_printf(s, "S%-13i %-14llu %-14llu %llu\n", i, idle_time, + genpd->states[i].usage, genpd->states[i].rejected); } genpd_unlock(genpd); @@ -3193,18 +3248,22 @@ static int idle_states_show(struct seq_file *s, void *data) static int active_time_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd = s->private; - ktime_t delta = 0; + u64 now, on_time, delta = 0; int ret = 0; ret = genpd_lock_interruptible(genpd); if (ret) return -ERESTARTSYS; - if (genpd->status == GENPD_STATE_ON) - delta = ktime_sub(ktime_get(), genpd->accounting_time); + if (genpd->status == GENPD_STATE_ON) { + now = ktime_get_mono_fast_ns(); + if (now > genpd->accounting_time) + delta = now - genpd->accounting_time; + } - seq_printf(s, "%lld ms\n", ktime_to_ms( - ktime_add(genpd->on_time, delta))); + on_time = genpd->on_time + delta; + do_div(on_time, NSEC_PER_MSEC); + seq_printf(s, "%llu ms\n", on_time); genpd_unlock(genpd); return ret; @@ -3213,7 +3272,7 @@ static int active_time_show(struct seq_file *s, void *data) static int total_idle_time_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd = s->private; - ktime_t delta = 0, total = 0; + u64 now, delta, total = 0; unsigned int i; int ret = 0; @@ -3222,16 +3281,19 @@ static int total_idle_time_show(struct seq_file *s, void *data) return -ERESTARTSYS; for (i = 0; i < genpd->state_count; i++) { + total += genpd->states[i].idle_time; - if ((genpd->status == GENPD_STATE_OFF) && - (genpd->state_idx == i)) - delta = ktime_sub(ktime_get(), genpd->accounting_time); - - total = ktime_add(total, genpd->states[i].idle_time); + if (genpd->status == GENPD_STATE_OFF && genpd->state_idx == i) { + now = ktime_get_mono_fast_ns(); + if (now > genpd->accounting_time) { + delta = now - genpd->accounting_time; + total += delta; + } + } } - total = ktime_add(total, delta); - seq_printf(s, "%lld ms\n", ktime_to_ms(total)); + do_div(total, NSEC_PER_MSEC); + seq_printf(s, "%llu ms\n", total); genpd_unlock(genpd); return ret; diff --git a/drivers/base/power/domain_governor.c b/drivers/base/power/domain_governor.c index 22424e7dbc34..88616eef7c8c 100644 --- a/drivers/base/power/domain_governor.c +++ b/drivers/base/power/domain_governor.c @@ -20,6 +20,8 @@ static int dev_update_qos_constraint(struct device *dev, void *data) s64 constraint_ns; if (dev->power.subsys_data && dev->power.subsys_data->domain_data) { + struct gpd_timing_data *td = dev_gpd_data(dev)->td; + /* * Only take suspend-time QoS constraints of devices into * account, because constraints updated after the device has @@ -27,7 +29,8 @@ static int dev_update_qos_constraint(struct device *dev, void *data) * anyway. In order for them to take effect, the device has to * be resumed and suspended again. */ - constraint_ns = dev_gpd_data(dev)->td.effective_constraint_ns; + constraint_ns = td ? td->effective_constraint_ns : + PM_QOS_RESUME_LATENCY_NO_CONSTRAINT_NS; } else { /* * The child is not in a domain and there's no info on its @@ -51,7 +54,7 @@ static int dev_update_qos_constraint(struct device *dev, void *data) */ static bool default_suspend_ok(struct device *dev) { - struct gpd_timing_data *td = &dev_gpd_data(dev)->td; + struct gpd_timing_data *td = dev_gpd_data(dev)->td; unsigned long flags; s64 constraint_ns; @@ -138,26 +141,28 @@ static void update_domain_next_wakeup(struct generic_pm_domain *genpd, ktime_t n * is able to enter its optimal idle state. */ list_for_each_entry(pdd, &genpd->dev_list, list_node) { - next_wakeup = to_gpd_data(pdd)->next_wakeup; + next_wakeup = to_gpd_data(pdd)->td->next_wakeup; if (next_wakeup != KTIME_MAX && !ktime_before(next_wakeup, now)) if (ktime_before(next_wakeup, domain_wakeup)) domain_wakeup = next_wakeup; } list_for_each_entry(link, &genpd->parent_links, parent_node) { - next_wakeup = link->child->next_wakeup; + struct genpd_governor_data *cgd = link->child->gd; + + next_wakeup = cgd ? cgd->next_wakeup : KTIME_MAX; if (next_wakeup != KTIME_MAX && !ktime_before(next_wakeup, now)) if (ktime_before(next_wakeup, domain_wakeup)) domain_wakeup = next_wakeup; } - genpd->next_wakeup = domain_wakeup; + genpd->gd->next_wakeup = domain_wakeup; } static bool next_wakeup_allows_state(struct generic_pm_domain *genpd, unsigned int state, ktime_t now) { - ktime_t domain_wakeup = genpd->next_wakeup; + ktime_t domain_wakeup = genpd->gd->next_wakeup; s64 idle_time_ns, min_sleep_ns; min_sleep_ns = genpd->states[state].power_off_latency_ns + @@ -192,8 +197,9 @@ static bool __default_power_down_ok(struct dev_pm_domain *pd, * All subdomains have been powered off already at this point. */ list_for_each_entry(link, &genpd->parent_links, parent_node) { - struct generic_pm_domain *sd = link->child; - s64 sd_max_off_ns = sd->max_off_time_ns; + struct genpd_governor_data *cgd = link->child->gd; + + s64 sd_max_off_ns = cgd ? cgd->max_off_time_ns : -1; if (sd_max_off_ns < 0) continue; @@ -222,7 +228,7 @@ static bool __default_power_down_ok(struct dev_pm_domain *pd, * domain to turn off and on (that's how much time it will * have to wait worst case). */ - td = &to_gpd_data(pdd)->td; + td = to_gpd_data(pdd)->td; constraint_ns = td->effective_constraint_ns; /* * Zero means "no suspend at all" and this runs only when all @@ -251,7 +257,7 @@ static bool __default_power_down_ok(struct dev_pm_domain *pd, * time and the time needed to turn the domain on is the maximum * theoretical time this domain can spend in the "off" state. */ - genpd->max_off_time_ns = min_off_time_ns - + genpd->gd->max_off_time_ns = min_off_time_ns - genpd->states[state].power_on_latency_ns; return true; } @@ -266,6 +272,7 @@ static bool __default_power_down_ok(struct dev_pm_domain *pd, static bool _default_power_down_ok(struct dev_pm_domain *pd, ktime_t now) { struct generic_pm_domain *genpd = pd_to_genpd(pd); + struct genpd_governor_data *gd = genpd->gd; int state_idx = genpd->state_count - 1; struct gpd_link *link; @@ -276,11 +283,11 @@ static bool _default_power_down_ok(struct dev_pm_domain *pd, ktime_t now) * cannot be met. */ update_domain_next_wakeup(genpd, now); - if ((genpd->flags & GENPD_FLAG_MIN_RESIDENCY) && (genpd->next_wakeup != KTIME_MAX)) { + if ((genpd->flags & GENPD_FLAG_MIN_RESIDENCY) && (gd->next_wakeup != KTIME_MAX)) { /* Let's find out the deepest domain idle state, the devices prefer */ while (state_idx >= 0) { if (next_wakeup_allows_state(genpd, state_idx, now)) { - genpd->max_off_time_changed = true; + gd->max_off_time_changed = true; break; } state_idx--; @@ -288,14 +295,14 @@ static bool _default_power_down_ok(struct dev_pm_domain *pd, ktime_t now) if (state_idx < 0) { state_idx = 0; - genpd->cached_power_down_ok = false; + gd->cached_power_down_ok = false; goto done; } } - if (!genpd->max_off_time_changed) { - genpd->state_idx = genpd->cached_power_down_state_idx; - return genpd->cached_power_down_ok; + if (!gd->max_off_time_changed) { + genpd->state_idx = gd->cached_power_down_state_idx; + return gd->cached_power_down_ok; } /* @@ -304,12 +311,16 @@ static bool _default_power_down_ok(struct dev_pm_domain *pd, ktime_t now) * going to be called for any parent until this instance * returns. */ - list_for_each_entry(link, &genpd->child_links, child_node) - link->parent->max_off_time_changed = true; + list_for_each_entry(link, &genpd->child_links, child_node) { + struct genpd_governor_data *pgd = link->parent->gd; - genpd->max_off_time_ns = -1; - genpd->max_off_time_changed = false; - genpd->cached_power_down_ok = true; + if (pgd) + pgd->max_off_time_changed = true; + } + + gd->max_off_time_ns = -1; + gd->max_off_time_changed = false; + gd->cached_power_down_ok = true; /* * Find a state to power down to, starting from the state @@ -317,7 +328,7 @@ static bool _default_power_down_ok(struct dev_pm_domain *pd, ktime_t now) */ while (!__default_power_down_ok(pd, state_idx)) { if (state_idx == 0) { - genpd->cached_power_down_ok = false; + gd->cached_power_down_ok = false; break; } state_idx--; @@ -325,8 +336,8 @@ static bool _default_power_down_ok(struct dev_pm_domain *pd, ktime_t now) done: genpd->state_idx = state_idx; - genpd->cached_power_down_state_idx = genpd->state_idx; - return genpd->cached_power_down_ok; + gd->cached_power_down_state_idx = genpd->state_idx; + return gd->cached_power_down_ok; } static bool default_power_down_ok(struct dev_pm_domain *pd) @@ -334,11 +345,6 @@ static bool default_power_down_ok(struct dev_pm_domain *pd) return _default_power_down_ok(pd, ktime_get()); } -static bool always_on_power_down_ok(struct dev_pm_domain *domain) -{ - return false; -} - #ifdef CONFIG_CPU_IDLE static bool cpu_power_down_ok(struct dev_pm_domain *pd) { @@ -408,6 +414,5 @@ struct dev_power_governor simple_qos_governor = { * pm_genpd_gov_always_on - A governor implementing an always-on policy */ struct dev_power_governor pm_domain_always_on_gov = { - .power_down_ok = always_on_power_down_ok, .suspend_ok = default_suspend_ok, }; diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index d4059e6ffeae..676dc72d912d 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -263,7 +263,7 @@ static int rpm_check_suspend_allowed(struct device *dev) retval = -EINVAL; else if (dev->power.disable_depth > 0) retval = -EACCES; - else if (atomic_read(&dev->power.usage_count) > 0) + else if (atomic_read(&dev->power.usage_count)) retval = -EAGAIN; else if (!dev->power.ignore_children && atomic_read(&dev->power.child_count)) @@ -1039,13 +1039,33 @@ int pm_schedule_suspend(struct device *dev, unsigned int delay) } EXPORT_SYMBOL_GPL(pm_schedule_suspend); +static int rpm_drop_usage_count(struct device *dev) +{ + int ret; + + ret = atomic_sub_return(1, &dev->power.usage_count); + if (ret >= 0) + return ret; + + /* + * Because rpm_resume() does not check the usage counter, it will resume + * the device even if the usage counter is 0 or negative, so it is + * sufficient to increment the usage counter here to reverse the change + * made above. + */ + atomic_inc(&dev->power.usage_count); + dev_warn(dev, "Runtime PM usage count underflow!\n"); + return -EINVAL; +} + /** * __pm_runtime_idle - Entry point for runtime idle operations. * @dev: Device to send idle notification for. * @rpmflags: Flag bits. * * If the RPM_GET_PUT flag is set, decrement the device's usage count and - * return immediately if it is larger than zero. Then carry out an idle + * return immediately if it is larger than zero (if it becomes negative, log a + * warning, increment it, and return an error). Then carry out an idle * notification, either synchronous or asynchronous. * * This routine may be called in atomic context if the RPM_ASYNC flag is set, @@ -1057,7 +1077,10 @@ int __pm_runtime_idle(struct device *dev, int rpmflags) int retval; if (rpmflags & RPM_GET_PUT) { - if (!atomic_dec_and_test(&dev->power.usage_count)) { + retval = rpm_drop_usage_count(dev); + if (retval < 0) { + return retval; + } else if (retval > 0) { trace_rpm_usage_rcuidle(dev, rpmflags); return 0; } @@ -1079,7 +1102,8 @@ EXPORT_SYMBOL_GPL(__pm_runtime_idle); * @rpmflags: Flag bits. * * If the RPM_GET_PUT flag is set, decrement the device's usage count and - * return immediately if it is larger than zero. Then carry out a suspend, + * return immediately if it is larger than zero (if it becomes negative, log a + * warning, increment it, and return an error). Then carry out a suspend, * either synchronous or asynchronous. * * This routine may be called in atomic context if the RPM_ASYNC flag is set, @@ -1091,7 +1115,10 @@ int __pm_runtime_suspend(struct device *dev, int rpmflags) int retval; if (rpmflags & RPM_GET_PUT) { - if (!atomic_dec_and_test(&dev->power.usage_count)) { + retval = rpm_drop_usage_count(dev); + if (retval < 0) { + return retval; + } else if (retval > 0) { trace_rpm_usage_rcuidle(dev, rpmflags); return 0; } @@ -1210,12 +1237,13 @@ int __pm_runtime_set_status(struct device *dev, unsigned int status) { struct device *parent = dev->parent; bool notify_parent = false; + unsigned long flags; int error = 0; if (status != RPM_ACTIVE && status != RPM_SUSPENDED) return -EINVAL; - spin_lock_irq(&dev->power.lock); + spin_lock_irqsave(&dev->power.lock, flags); /* * Prevent PM-runtime from being enabled for the device or return an @@ -1226,7 +1254,7 @@ int __pm_runtime_set_status(struct device *dev, unsigned int status) else error = -EAGAIN; - spin_unlock_irq(&dev->power.lock); + spin_unlock_irqrestore(&dev->power.lock, flags); if (error) return error; @@ -1247,7 +1275,7 @@ int __pm_runtime_set_status(struct device *dev, unsigned int status) device_links_read_unlock(idx); } - spin_lock_irq(&dev->power.lock); + spin_lock_irqsave(&dev->power.lock, flags); if (dev->power.runtime_status == status || !parent) goto out_set; @@ -1288,7 +1316,7 @@ int __pm_runtime_set_status(struct device *dev, unsigned int status) dev->power.runtime_error = 0; out: - spin_unlock_irq(&dev->power.lock); + spin_unlock_irqrestore(&dev->power.lock, flags); if (notify_parent) pm_request_idle(parent); @@ -1527,14 +1555,17 @@ EXPORT_SYMBOL_GPL(pm_runtime_forbid); */ void pm_runtime_allow(struct device *dev) { + int ret; + spin_lock_irq(&dev->power.lock); if (dev->power.runtime_auto) goto out; dev->power.runtime_auto = true; - if (atomic_dec_and_test(&dev->power.usage_count)) + ret = rpm_drop_usage_count(dev); + if (ret == 0) rpm_idle(dev, RPM_AUTO | RPM_ASYNC); - else + else if (ret > 0) trace_rpm_usage_rcuidle(dev, RPM_AUTO | RPM_ASYNC); out: diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index b4df36c7b17d..da8996e7a1f1 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -110,6 +110,10 @@ struct regmap { int (*reg_write)(void *context, unsigned int reg, unsigned int val); int (*reg_update_bits)(void *context, unsigned int reg, unsigned int mask, unsigned int val); + /* Bulk read/write */ + int (*read)(void *context, const void *reg_buf, size_t reg_size, + void *val_buf, size_t val_size); + int (*write)(void *context, const void *data, size_t count); bool defer_caching; diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index f2469d3435ca..d0f5bc827978 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -183,8 +183,8 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) return 0; } - if (!map->max_register) - map->max_register = map->num_reg_defaults_raw; + if (!map->max_register && map->num_reg_defaults_raw) + map->max_register = (map->num_reg_defaults_raw - 1) * map->reg_stride; if (map->cache_ops->init) { dev_dbg(map->dev, "Initializing %s cache\n", diff --git a/drivers/base/regmap/regmap-i3c.c b/drivers/base/regmap/regmap-i3c.c index 1578fb506683..0328b0b34284 100644 --- a/drivers/base/regmap/regmap-i3c.c +++ b/drivers/base/regmap/regmap-i3c.c @@ -40,7 +40,7 @@ static int regmap_i3c_read(void *context, return i3c_device_do_priv_xfers(i3c, xfers, 2); } -static struct regmap_bus regmap_i3c = { +static const struct regmap_bus regmap_i3c = { .write = regmap_i3c_write, .read = regmap_i3c_read, }; diff --git a/drivers/base/regmap/regmap-sccb.c b/drivers/base/regmap/regmap-sccb.c index 597042e2d009..986af26d88c2 100644 --- a/drivers/base/regmap/regmap-sccb.c +++ b/drivers/base/regmap/regmap-sccb.c @@ -80,7 +80,7 @@ static int regmap_sccb_write(void *context, unsigned int reg, unsigned int val) return i2c_smbus_write_byte_data(i2c, reg, val); } -static struct regmap_bus regmap_sccb_bus = { +static const struct regmap_bus regmap_sccb_bus = { .reg_write = regmap_sccb_write, .reg_read = regmap_sccb_read, }; diff --git a/drivers/base/regmap/regmap-sdw-mbq.c b/drivers/base/regmap/regmap-sdw-mbq.c index fe3ac26b66ad..388c3a087bd9 100644 --- a/drivers/base/regmap/regmap-sdw-mbq.c +++ b/drivers/base/regmap/regmap-sdw-mbq.c @@ -42,7 +42,7 @@ static int regmap_sdw_mbq_read(void *context, unsigned int reg, unsigned int *va return 0; } -static struct regmap_bus regmap_sdw_mbq = { +static const struct regmap_bus regmap_sdw_mbq = { .reg_read = regmap_sdw_mbq_read, .reg_write = regmap_sdw_mbq_write, .reg_format_endian_default = REGMAP_ENDIAN_LITTLE, diff --git a/drivers/base/regmap/regmap-sdw.c b/drivers/base/regmap/regmap-sdw.c index 966de8a136d9..81b0327f719d 100644 --- a/drivers/base/regmap/regmap-sdw.c +++ b/drivers/base/regmap/regmap-sdw.c @@ -30,7 +30,7 @@ static int regmap_sdw_read(void *context, unsigned int reg, unsigned int *val) return 0; } -static struct regmap_bus regmap_sdw = { +static const struct regmap_bus regmap_sdw = { .reg_read = regmap_sdw_read, .reg_write = regmap_sdw_write, .reg_format_endian_default = REGMAP_ENDIAN_LITTLE, diff --git a/drivers/base/regmap/regmap-slimbus.c b/drivers/base/regmap/regmap-slimbus.c index 0968059f1ef5..8075db788b39 100644 --- a/drivers/base/regmap/regmap-slimbus.c +++ b/drivers/base/regmap/regmap-slimbus.c @@ -22,7 +22,7 @@ static int regmap_slimbus_read(void *context, const void *reg, size_t reg_size, return slim_read(sdev, *(u16 *)reg, val_size, val); } -static struct regmap_bus regmap_slimbus_bus = { +static const struct regmap_bus regmap_slimbus_bus = { .write = regmap_slimbus_write, .read = regmap_slimbus_read, .reg_format_endian_default = REGMAP_ENDIAN_LITTLE, diff --git a/drivers/base/regmap/regmap-w1.c b/drivers/base/regmap/regmap-w1.c index 1fbaaad71ca5..3a8b402db852 100644 --- a/drivers/base/regmap/regmap-w1.c +++ b/drivers/base/regmap/regmap-w1.c @@ -172,17 +172,17 @@ static int w1_reg_a16_v16_write(void *context, unsigned int reg, * Various types of supported bus addressing */ -static struct regmap_bus regmap_w1_bus_a8_v8 = { +static const struct regmap_bus regmap_w1_bus_a8_v8 = { .reg_read = w1_reg_a8_v8_read, .reg_write = w1_reg_a8_v8_write, }; -static struct regmap_bus regmap_w1_bus_a8_v16 = { +static const struct regmap_bus regmap_w1_bus_a8_v16 = { .reg_read = w1_reg_a8_v16_read, .reg_write = w1_reg_a8_v16_write, }; -static struct regmap_bus regmap_w1_bus_a16_v16 = { +static const struct regmap_bus regmap_w1_bus_a16_v16 = { .reg_read = w1_reg_a16_v16_read, .reg_write = w1_reg_a16_v16_write, }; diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 5e12f7cb5147..2221d9863831 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -838,12 +838,15 @@ struct regmap *__regmap_init(struct device *dev, map->reg_stride_order = ilog2(map->reg_stride); else map->reg_stride_order = -1; - map->use_single_read = config->use_single_read || !bus || !bus->read; - map->use_single_write = config->use_single_write || !bus || !bus->write; - map->can_multi_write = config->can_multi_write && bus && bus->write; + map->use_single_read = config->use_single_read || !(config->read || (bus && bus->read)); + map->use_single_write = config->use_single_write || !(config->write || (bus && bus->write)); + map->can_multi_write = config->can_multi_write && (config->write || (bus && bus->write)); if (bus) { map->max_raw_read = bus->max_raw_read; map->max_raw_write = bus->max_raw_write; + } else if (config->max_raw_read && config->max_raw_write) { + map->max_raw_read = config->max_raw_read; + map->max_raw_write = config->max_raw_write; } map->dev = dev; map->bus = bus; @@ -877,7 +880,16 @@ struct regmap *__regmap_init(struct device *dev, map->read_flag_mask = bus->read_flag_mask; } - if (!bus) { + if (config && config->read && config->write) { + map->reg_read = _regmap_bus_read; + + /* Bulk read/write */ + map->read = config->read; + map->write = config->write; + + reg_endian = REGMAP_ENDIAN_NATIVE; + val_endian = REGMAP_ENDIAN_NATIVE; + } else if (!bus) { map->reg_read = config->reg_read; map->reg_write = config->reg_write; map->reg_update_bits = config->reg_update_bits; @@ -894,10 +906,13 @@ struct regmap *__regmap_init(struct device *dev, } else { map->reg_read = _regmap_bus_read; map->reg_update_bits = bus->reg_update_bits; - } + /* Bulk read/write */ + map->read = bus->read; + map->write = bus->write; - reg_endian = regmap_get_reg_endian(bus, config); - val_endian = regmap_get_val_endian(dev, bus, config); + reg_endian = regmap_get_reg_endian(bus, config); + val_endian = regmap_get_val_endian(dev, bus, config); + } switch (config->reg_bits + map->reg_shift) { case 2: @@ -1671,8 +1686,6 @@ static int _regmap_raw_write_impl(struct regmap *map, unsigned int reg, size_t len; int i; - WARN_ON(!map->bus); - /* Check for unwritable or noinc registers in range * before we start */ @@ -1754,7 +1767,7 @@ static int _regmap_raw_write_impl(struct regmap *map, unsigned int reg, val = work_val; } - if (map->async && map->bus->async_write) { + if (map->async && map->bus && map->bus->async_write) { struct regmap_async *async; trace_regmap_async_write_start(map, reg, val_len); @@ -1822,11 +1835,11 @@ static int _regmap_raw_write_impl(struct regmap *map, unsigned int reg, * write. */ if (val == work_val) - ret = map->bus->write(map->bus_context, map->work_buf, - map->format.reg_bytes + - map->format.pad_bytes + - val_len); - else if (map->bus->gather_write) + ret = map->write(map->bus_context, map->work_buf, + map->format.reg_bytes + + map->format.pad_bytes + + val_len); + else if (map->bus && map->bus->gather_write) ret = map->bus->gather_write(map->bus_context, map->work_buf, map->format.reg_bytes + map->format.pad_bytes, @@ -1844,7 +1857,7 @@ static int _regmap_raw_write_impl(struct regmap *map, unsigned int reg, memcpy(buf, map->work_buf, map->format.reg_bytes); memcpy(buf + map->format.reg_bytes + map->format.pad_bytes, val, val_len); - ret = map->bus->write(map->bus_context, buf, len); + ret = map->write(map->bus_context, buf, len); kfree(buf); } else if (ret != 0 && !map->cache_bypass && map->format.parse_val) { @@ -1901,7 +1914,7 @@ static int _regmap_bus_formatted_write(void *context, unsigned int reg, struct regmap_range_node *range; struct regmap *map = context; - WARN_ON(!map->bus || !map->format.format_write); + WARN_ON(!map->format.format_write); range = _regmap_range_lookup(map, reg); if (range) { @@ -1916,8 +1929,7 @@ static int _regmap_bus_formatted_write(void *context, unsigned int reg, trace_regmap_hw_write_start(map, reg, 1); - ret = map->bus->write(map->bus_context, map->work_buf, - map->format.buf_size); + ret = map->write(map->bus_context, map->work_buf, map->format.buf_size); trace_regmap_hw_write_done(map, reg, 1); @@ -1937,7 +1949,7 @@ static int _regmap_bus_raw_write(void *context, unsigned int reg, { struct regmap *map = context; - WARN_ON(!map->bus || !map->format.format_val); + WARN_ON(!map->format.format_val); map->format.format_val(map->work_buf + map->format.reg_bytes + map->format.pad_bytes, val, 0); @@ -1951,7 +1963,7 @@ static int _regmap_bus_raw_write(void *context, unsigned int reg, static inline void *_regmap_map_get_context(struct regmap *map) { - return (map->bus) ? map : map->bus_context; + return (map->bus || (!map->bus && map->read)) ? map : map->bus_context; } int _regmap_write(struct regmap *map, unsigned int reg, @@ -2363,7 +2375,7 @@ static int _regmap_raw_multi_reg_write(struct regmap *map, u8 = buf; *u8 |= map->write_flag_mask; - ret = map->bus->write(map->bus_context, buf, len); + ret = map->write(map->bus_context, buf, len); kfree(buf); @@ -2669,9 +2681,7 @@ static int _regmap_raw_read(struct regmap *map, unsigned int reg, void *val, struct regmap_range_node *range; int ret; - WARN_ON(!map->bus); - - if (!map->bus || !map->bus->read) + if (!map->read) return -EINVAL; range = _regmap_range_lookup(map, reg); @@ -2689,9 +2699,9 @@ static int _regmap_raw_read(struct regmap *map, unsigned int reg, void *val, map->read_flag_mask); trace_regmap_hw_read_start(map, reg, val_len / map->format.val_bytes); - ret = map->bus->read(map->bus_context, map->work_buf, - map->format.reg_bytes + map->format.pad_bytes, - val, val_len); + ret = map->read(map->bus_context, map->work_buf, + map->format.reg_bytes + map->format.pad_bytes, + val, val_len); trace_regmap_hw_read_done(map, reg, val_len / map->format.val_bytes); @@ -2802,8 +2812,6 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, unsigned int v; int ret, i; - if (!map->bus) - return -EINVAL; if (val_len % map->format.val_bytes) return -EINVAL; if (!IS_ALIGNED(reg, map->reg_stride)) @@ -2818,7 +2826,7 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, size_t chunk_count, chunk_bytes; size_t chunk_regs = val_count; - if (!map->bus->read) { + if (!map->read) { ret = -ENOTSUPP; goto out; } @@ -2878,7 +2886,7 @@ EXPORT_SYMBOL_GPL(regmap_raw_read); * @val: Pointer to data buffer * @val_len: Length of output buffer in bytes. * - * The regmap API usually assumes that bulk bus read operations will read a + * The regmap API usually assumes that bulk read operations will read a * range of registers. Some devices have certain registers for which a read * operation read will read from an internal FIFO. * @@ -2896,10 +2904,6 @@ int regmap_noinc_read(struct regmap *map, unsigned int reg, size_t read_len; int ret; - if (!map->bus) - return -EINVAL; - if (!map->bus->read) - return -ENOTSUPP; if (val_len % map->format.val_bytes) return -EINVAL; if (!IS_ALIGNED(reg, map->reg_stride)) @@ -3013,7 +3017,7 @@ int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, if (val_count == 0) return -EINVAL; - if (map->bus && map->format.parse_inplace && (vol || map->cache_type == REGCACHE_NONE)) { + if (map->format.parse_inplace && (vol || map->cache_type == REGCACHE_NONE)) { ret = regmap_raw_read(map, reg, val, val_bytes * val_count); if (ret != 0) return ret; diff --git a/drivers/char/ipmi/ipmb_dev_int.c b/drivers/char/ipmi/ipmb_dev_int.c index 49b8f22fdcf0..db40037eb347 100644 --- a/drivers/char/ipmi/ipmb_dev_int.c +++ b/drivers/char/ipmi/ipmb_dev_int.c @@ -299,8 +299,7 @@ static int ipmb_slave_cb(struct i2c_client *client, return 0; } -static int ipmb_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int ipmb_probe(struct i2c_client *client) { struct ipmb_dev *ipmb_dev; int ret; @@ -369,7 +368,7 @@ static struct i2c_driver ipmb_driver = { .name = "ipmb-dev", .acpi_match_table = ACPI_PTR(acpi_ipmb_id), }, - .probe = ipmb_probe, + .probe_new = ipmb_probe, .remove = ipmb_remove, .id_table = ipmb_id, }; diff --git a/drivers/char/ipmi/ipmi_ipmb.c b/drivers/char/ipmi/ipmi_ipmb.c index b81b862532fb..ab19b4b3317e 100644 --- a/drivers/char/ipmi/ipmi_ipmb.c +++ b/drivers/char/ipmi/ipmi_ipmb.c @@ -442,8 +442,7 @@ static int ipmi_ipmb_remove(struct i2c_client *client) return 0; } -static int ipmi_ipmb_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int ipmi_ipmb_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct ipmi_ipmb_dev *iidev; @@ -476,6 +475,7 @@ static int ipmi_ipmb_probe(struct i2c_client *client, slave_np = of_parse_phandle(dev->of_node, "slave-dev", 0); if (slave_np) { slave_adap = of_get_i2c_adapter_by_node(slave_np); + of_node_put(slave_np); if (!slave_adap) { dev_notice(&client->dev, "Could not find slave adapter\n"); @@ -570,7 +570,7 @@ static struct i2c_driver ipmi_ipmb_driver = { .name = DEVICE_NAME, .of_match_table = of_ipmi_ipmb_match, }, - .probe = ipmi_ipmb_probe, + .probe_new = ipmi_ipmb_probe, .remove = ipmi_ipmb_remove, .id_table = ipmi_ipmb_id, }; diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index f1827257ef0e..703433493c85 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -11,8 +11,8 @@ * Copyright 2002 MontaVista Software Inc. */ -#define pr_fmt(fmt) "%s" fmt, "IPMI message handler: " -#define dev_fmt pr_fmt +#define pr_fmt(fmt) "IPMI message handler: " fmt +#define dev_fmt(fmt) pr_fmt(fmt) #include #include @@ -145,6 +145,18 @@ module_param(default_max_retries, uint, 0644); MODULE_PARM_DESC(default_max_retries, "The time (milliseconds) between retry sends in maintenance mode"); +/* The default maximum number of users that may register. */ +static unsigned int max_users = 30; +module_param(max_users, uint, 0644); +MODULE_PARM_DESC(max_users, + "The most users that may use the IPMI stack at one time."); + +/* The default maximum number of message a user may have outstanding. */ +static unsigned int max_msgs_per_user = 100; +module_param(max_msgs_per_user, uint, 0644); +MODULE_PARM_DESC(max_msgs_per_user, + "The most message a user may have outstanding."); + /* Call every ~1000 ms. */ #define IPMI_TIMEOUT_TIME 1000 @@ -187,6 +199,8 @@ struct ipmi_user { /* Does this interface receive IPMI events? */ bool gets_events; + atomic_t nr_msgs; + /* Free must run in process context for RCU cleanup. */ struct work_struct remove_work; }; @@ -442,6 +456,10 @@ struct ipmi_smi { */ struct list_head users; struct srcu_struct users_srcu; + atomic_t nr_users; + struct device_attribute nr_users_devattr; + struct device_attribute nr_msgs_devattr; + /* Used for wake ups at startup. */ wait_queue_head_t waitq; @@ -927,11 +945,13 @@ static int deliver_response(struct ipmi_smi *intf, struct ipmi_recv_msg *msg) * risk. At this moment, simply skip it in that case. */ ipmi_free_recv_msg(msg); + atomic_dec(&msg->user->nr_msgs); } else { int index; struct ipmi_user *user = acquire_ipmi_user(msg->user, &index); if (user) { + atomic_dec(&user->nr_msgs); user->handler->ipmi_recv_hndl(msg, user->handler_data); release_ipmi_user(user, index); } else { @@ -1230,6 +1250,11 @@ int ipmi_create_user(unsigned int if_num, goto out_kfree; found: + if (atomic_add_return(1, &intf->nr_users) > max_users) { + rv = -EBUSY; + goto out_kfree; + } + INIT_WORK(&new_user->remove_work, free_user_work); rv = init_srcu_struct(&new_user->release_barrier); @@ -1244,6 +1269,7 @@ int ipmi_create_user(unsigned int if_num, /* Note that each existing user holds a refcount to the interface. */ kref_get(&intf->refcount); + atomic_set(&new_user->nr_msgs, 0); kref_init(&new_user->refcount); new_user->handler = handler; new_user->handler_data = handler_data; @@ -1262,6 +1288,7 @@ int ipmi_create_user(unsigned int if_num, return 0; out_kfree: + atomic_dec(&intf->nr_users); srcu_read_unlock(&ipmi_interfaces_srcu, index); vfree(new_user); return rv; @@ -1336,6 +1363,7 @@ static void _ipmi_destroy_user(struct ipmi_user *user) /* Remove the user from the interface's sequence table. */ spin_lock_irqsave(&intf->seq_lock, flags); list_del_rcu(&user->link); + atomic_dec(&intf->nr_users); for (i = 0; i < IPMI_IPMB_NUM_SEQ; i++) { if (intf->seq_table[i].inuse @@ -2284,6 +2312,14 @@ static int i_ipmi_request(struct ipmi_user *user, struct ipmi_recv_msg *recv_msg; int rv = 0; + if (user) { + if (atomic_add_return(1, &user->nr_msgs) > max_msgs_per_user) { + /* Decrement will happen at the end of the routine. */ + rv = -EBUSY; + goto out; + } + } + if (supplied_recv) recv_msg = supplied_recv; else { @@ -2296,7 +2332,7 @@ static int i_ipmi_request(struct ipmi_user *user, recv_msg->user_msg_data = user_msg_data; if (supplied_smi) - smi_msg = (struct ipmi_smi_msg *) supplied_smi; + smi_msg = supplied_smi; else { smi_msg = ipmi_alloc_smi_msg(); if (smi_msg == NULL) { @@ -2348,13 +2384,16 @@ out_err: ipmi_free_smi_msg(smi_msg); ipmi_free_recv_msg(recv_msg); } else { - pr_debug("Send: %*ph\n", smi_msg->data_size, smi_msg->data); + dev_dbg(intf->si_dev, "Send: %*ph\n", + smi_msg->data_size, smi_msg->data); smi_send(intf, intf->handlers, smi_msg, priority); } rcu_read_unlock(); out: + if (rv && user) + atomic_dec(&user->nr_msgs); return rv; } @@ -3471,6 +3510,36 @@ void ipmi_poll_interface(struct ipmi_user *user) } EXPORT_SYMBOL(ipmi_poll_interface); +static ssize_t nr_users_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ipmi_smi *intf = container_of(attr, + struct ipmi_smi, nr_users_devattr); + + return sysfs_emit(buf, "%d\n", atomic_read(&intf->nr_users)); +} +static DEVICE_ATTR_RO(nr_users); + +static ssize_t nr_msgs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ipmi_smi *intf = container_of(attr, + struct ipmi_smi, nr_msgs_devattr); + struct ipmi_user *user; + int index; + unsigned int count = 0; + + index = srcu_read_lock(&intf->users_srcu); + list_for_each_entry_rcu(user, &intf->users, link) + count += atomic_read(&user->nr_msgs); + srcu_read_unlock(&intf->users_srcu, index); + + return sysfs_emit(buf, "%u\n", count); +} +static DEVICE_ATTR_RO(nr_msgs); + static void redo_bmc_reg(struct work_struct *work) { struct ipmi_smi *intf = container_of(work, struct ipmi_smi, @@ -3529,6 +3598,7 @@ int ipmi_add_smi(struct module *owner, if (slave_addr != 0) intf->addrinfo[0].address = slave_addr; INIT_LIST_HEAD(&intf->users); + atomic_set(&intf->nr_users, 0); intf->handlers = handlers; intf->send_info = send_info; spin_lock_init(&intf->seq_lock); @@ -3592,6 +3662,20 @@ int ipmi_add_smi(struct module *owner, if (rv) goto out_err_bmc_reg; + intf->nr_users_devattr = dev_attr_nr_users; + sysfs_attr_init(&intf->nr_users_devattr.attr); + rv = device_create_file(intf->si_dev, &intf->nr_users_devattr); + if (rv) + goto out_err_bmc_reg; + + intf->nr_msgs_devattr = dev_attr_nr_msgs; + sysfs_attr_init(&intf->nr_msgs_devattr.attr); + rv = device_create_file(intf->si_dev, &intf->nr_msgs_devattr); + if (rv) { + device_remove_file(intf->si_dev, &intf->nr_users_devattr); + goto out_err_bmc_reg; + } + /* * Keep memory order straight for RCU readers. Make * sure everything else is committed to memory before @@ -3691,6 +3775,9 @@ void ipmi_unregister_smi(struct ipmi_smi *intf) /* At this point no users can be added to the interface. */ + device_remove_file(intf->si_dev, &intf->nr_msgs_devattr); + device_remove_file(intf->si_dev, &intf->nr_users_devattr); + /* * Call all the watcher interfaces to tell them that * an interface is going away. @@ -3839,7 +3926,8 @@ static int handle_ipmb_get_msg_cmd(struct ipmi_smi *intf, msg->data[10] = ipmb_checksum(&msg->data[6], 4); msg->data_size = 11; - pr_debug("Invalid command: %*ph\n", msg->data_size, msg->data); + dev_dbg(intf->si_dev, "Invalid command: %*ph\n", + msg->data_size, msg->data); rcu_read_lock(); if (!intf->in_shutdown) { @@ -3992,10 +4080,10 @@ static int handle_ipmb_direct_rcv_rsp(struct ipmi_smi *intf, struct ipmi_recv_msg *recv_msg; struct ipmi_ipmb_direct_addr *daddr; - recv_msg = (struct ipmi_recv_msg *) msg->user_data; + recv_msg = msg->user_data; if (recv_msg == NULL) { dev_warn(intf->si_dev, - "IPMI message received with no owner. This could be because of a malformed message, or because of a hardware error. Contact your hardware vendor for assistance.\n"); + "IPMI direct message received with no owner. This could be because of a malformed message, or because of a hardware error. Contact your hardware vendor for assistance.\n"); return 0; } @@ -4410,10 +4498,10 @@ static int handle_bmc_rsp(struct ipmi_smi *intf, struct ipmi_recv_msg *recv_msg; struct ipmi_system_interface_addr *smi_addr; - recv_msg = (struct ipmi_recv_msg *) msg->user_data; + recv_msg = msg->user_data; if (recv_msg == NULL) { dev_warn(intf->si_dev, - "IPMI message received with no owner. This could be because of a malformed message, or because of a hardware error. Contact your hardware vendor for assistance.\n"); + "IPMI SMI message received with no owner. This could be because of a malformed message, or because of a hardware error. Contact your hardware vendor for assistance.\n"); return 0; } @@ -4447,7 +4535,7 @@ static int handle_one_recv_msg(struct ipmi_smi *intf, unsigned char cc; bool is_cmd = !((msg->rsp[0] >> 2) & 1); - pr_debug("Recv: %*ph\n", msg->rsp_size, msg->rsp); + dev_dbg(intf->si_dev, "Recv: %*ph\n", msg->rsp_size, msg->rsp); if (msg->rsp_size < 2) { /* Message is too small to be correct. */ @@ -4831,7 +4919,8 @@ smi_from_recv_msg(struct ipmi_smi *intf, struct ipmi_recv_msg *recv_msg, smi_msg->data_size = recv_msg->msg.data_len; smi_msg->msgid = STORE_SEQ_IN_MSGID(seq, seqid); - pr_debug("Resend: %*ph\n", smi_msg->data_size, smi_msg->data); + dev_dbg(intf->si_dev, "Resend: %*ph\n", + smi_msg->data_size, smi_msg->data); return smi_msg; } diff --git a/drivers/char/ipmi/ipmi_poweroff.c b/drivers/char/ipmi/ipmi_poweroff.c index bc3a18daf97a..163ec9749e55 100644 --- a/drivers/char/ipmi/ipmi_poweroff.c +++ b/drivers/char/ipmi/ipmi_poweroff.c @@ -94,12 +94,8 @@ static void dummy_recv_free(struct ipmi_recv_msg *msg) { atomic_dec(&dummy_count); } -static struct ipmi_smi_msg halt_smi_msg = { - .done = dummy_smi_free -}; -static struct ipmi_recv_msg halt_recv_msg = { - .done = dummy_recv_free -}; +static struct ipmi_smi_msg halt_smi_msg = INIT_IPMI_SMI_MSG(dummy_smi_free); +static struct ipmi_recv_msg halt_recv_msg = INIT_IPMI_RECV_MSG(dummy_recv_free); /* diff --git a/drivers/char/ipmi/ipmi_si_intf.c b/drivers/char/ipmi/ipmi_si_intf.c index 5604a810fb3d..6e357ad76f2e 100644 --- a/drivers/char/ipmi/ipmi_si_intf.c +++ b/drivers/char/ipmi/ipmi_si_intf.c @@ -264,15 +264,16 @@ static void cleanup_one_si(struct smi_info *smi_info); static void cleanup_ipmi_si(void); #ifdef DEBUG_TIMING -void debug_timestamp(char *msg) +void debug_timestamp(struct smi_info *smi_info, char *msg) { struct timespec64 t; ktime_get_ts64(&t); - pr_debug("**%s: %lld.%9.9ld\n", msg, t.tv_sec, t.tv_nsec); + dev_dbg(smi_info->io.dev, "**%s: %lld.%9.9ld\n", + msg, t.tv_sec, t.tv_nsec); } #else -#define debug_timestamp(x) +#define debug_timestamp(smi_info, x) #endif static ATOMIC_NOTIFIER_HEAD(xaction_notifier_list); @@ -318,7 +319,7 @@ static enum si_sm_result start_next_msg(struct smi_info *smi_info) smi_info->curr_msg = smi_info->waiting_msg; smi_info->waiting_msg = NULL; - debug_timestamp("Start2"); + debug_timestamp(smi_info, "Start2"); err = atomic_notifier_call_chain(&xaction_notifier_list, 0, smi_info); if (err & NOTIFY_STOP_MASK) { @@ -538,7 +539,7 @@ static void handle_transaction_done(struct smi_info *smi_info) { struct ipmi_smi_msg *msg; - debug_timestamp("Done"); + debug_timestamp(smi_info, "Done"); switch (smi_info->si_state) { case SI_NORMAL: if (!smi_info->curr_msg) @@ -901,7 +902,7 @@ static void sender(void *send_info, struct smi_info *smi_info = send_info; unsigned long flags; - debug_timestamp("Enqueue"); + debug_timestamp(smi_info, "Enqueue"); if (smi_info->run_to_completion) { /* @@ -1079,7 +1080,7 @@ static void smi_timeout(struct timer_list *t) long timeout; spin_lock_irqsave(&(smi_info->si_lock), flags); - debug_timestamp("Timer"); + debug_timestamp(smi_info, "Timer"); jiffies_now = jiffies; time_diff = (((long)jiffies_now - (long)smi_info->last_timeout_jiffies) @@ -1128,7 +1129,7 @@ irqreturn_t ipmi_si_irq_handler(int irq, void *data) smi_inc_stat(smi_info, interrupts); - debug_timestamp("Interrupt"); + debug_timestamp(smi_info, "Interrupt"); smi_event_handler(smi_info, 0); spin_unlock_irqrestore(&(smi_info->si_lock), flags); diff --git a/drivers/char/ipmi/ipmi_ssif.c b/drivers/char/ipmi/ipmi_ssif.c index f199cc194844..fc742ee9c046 100644 --- a/drivers/char/ipmi/ipmi_ssif.c +++ b/drivers/char/ipmi/ipmi_ssif.c @@ -814,6 +814,14 @@ static void msg_done_handler(struct ssif_info *ssif_info, int result, break; case SSIF_GETTING_EVENTS: + if (!msg) { + /* Should never happen, but just in case. */ + dev_warn(&ssif_info->client->dev, + "No message set while getting events\n"); + ipmi_ssif_unlock_cond(ssif_info, flags); + break; + } + if ((result < 0) || (len < 3) || (msg->rsp[2] != 0)) { /* Error getting event, probably done. */ msg->done(msg); @@ -838,6 +846,14 @@ static void msg_done_handler(struct ssif_info *ssif_info, int result, break; case SSIF_GETTING_MESSAGES: + if (!msg) { + /* Should never happen, but just in case. */ + dev_warn(&ssif_info->client->dev, + "No message set while getting messages\n"); + ipmi_ssif_unlock_cond(ssif_info, flags); + break; + } + if ((result < 0) || (len < 3) || (msg->rsp[2] != 0)) { /* Error getting event, probably done. */ msg->done(msg); @@ -861,6 +877,13 @@ static void msg_done_handler(struct ssif_info *ssif_info, int result, deliver_recv_msg(ssif_info, msg); } break; + + default: + /* Should never happen, but just in case. */ + dev_warn(&ssif_info->client->dev, + "Invalid state in message done handling: %d\n", + ssif_info->ssif_state); + ipmi_ssif_unlock_cond(ssif_info, flags); } flags = ipmi_ssif_lock_cond(ssif_info, &oflags); @@ -1053,7 +1076,7 @@ static void start_next_msg(struct ssif_info *ssif_info, unsigned long *flags) static void sender(void *send_info, struct ipmi_smi_msg *msg) { - struct ssif_info *ssif_info = (struct ssif_info *) send_info; + struct ssif_info *ssif_info = send_info; unsigned long oflags, *flags; BUG_ON(ssif_info->waiting_msg); @@ -1090,7 +1113,7 @@ static int get_smi_info(void *send_info, struct ipmi_smi_info *data) */ static void request_events(void *send_info) { - struct ssif_info *ssif_info = (struct ssif_info *) send_info; + struct ssif_info *ssif_info = send_info; unsigned long oflags, *flags; if (!ssif_info->has_event_buffer) @@ -1107,7 +1130,7 @@ static void request_events(void *send_info) */ static void ssif_set_need_watch(void *send_info, unsigned int watch_mask) { - struct ssif_info *ssif_info = (struct ssif_info *) send_info; + struct ssif_info *ssif_info = send_info; unsigned long oflags, *flags; long timeout = 0; @@ -1619,7 +1642,7 @@ static int ssif_check_and_remove(struct i2c_client *client, return 0; } -static int ssif_probe(struct i2c_client *client, const struct i2c_device_id *id) +static int ssif_probe(struct i2c_client *client) { unsigned char msg[3]; unsigned char *resp; @@ -2037,7 +2060,7 @@ static struct i2c_driver ssif_i2c_driver = { .driver = { .name = DEVICE_NAME }, - .probe = ssif_probe, + .probe_new = ssif_probe, .remove = ssif_remove, .alert = ssif_alert, .id_table = ssif_id, diff --git a/drivers/char/ipmi/ipmi_watchdog.c b/drivers/char/ipmi/ipmi_watchdog.c index 0604abdd249a..5b4e677929ca 100644 --- a/drivers/char/ipmi/ipmi_watchdog.c +++ b/drivers/char/ipmi/ipmi_watchdog.c @@ -354,12 +354,8 @@ static void msg_free_recv(struct ipmi_recv_msg *msg) complete(&msg_wait); } } -static struct ipmi_smi_msg smi_msg = { - .done = msg_free_smi -}; -static struct ipmi_recv_msg recv_msg = { - .done = msg_free_recv -}; +static struct ipmi_smi_msg smi_msg = INIT_IPMI_SMI_MSG(msg_free_smi); +static struct ipmi_recv_msg recv_msg = INIT_IPMI_RECV_MSG(msg_free_recv); static int __ipmi_set_timeout(struct ipmi_smi_msg *smi_msg, struct ipmi_recv_msg *recv_msg, @@ -475,12 +471,10 @@ static void panic_recv_free(struct ipmi_recv_msg *msg) atomic_dec(&panic_done_count); } -static struct ipmi_smi_msg panic_halt_heartbeat_smi_msg = { - .done = panic_smi_free -}; -static struct ipmi_recv_msg panic_halt_heartbeat_recv_msg = { - .done = panic_recv_free -}; +static struct ipmi_smi_msg panic_halt_heartbeat_smi_msg = + INIT_IPMI_SMI_MSG(panic_smi_free); +static struct ipmi_recv_msg panic_halt_heartbeat_recv_msg = + INIT_IPMI_RECV_MSG(panic_recv_free); static void panic_halt_ipmi_heartbeat(void) { @@ -516,12 +510,10 @@ static void panic_halt_ipmi_heartbeat(void) atomic_sub(2, &panic_done_count); } -static struct ipmi_smi_msg panic_halt_smi_msg = { - .done = panic_smi_free -}; -static struct ipmi_recv_msg panic_halt_recv_msg = { - .done = panic_recv_free -}; +static struct ipmi_smi_msg panic_halt_smi_msg = + INIT_IPMI_SMI_MSG(panic_smi_free); +static struct ipmi_recv_msg panic_halt_recv_msg = + INIT_IPMI_RECV_MSG(panic_recv_free); /* * Special call, doesn't claim any locks. This is only to be called diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index 4704fa553098..c1eb5d223839 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -400,7 +400,16 @@ ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id, u32 *value, if (!rc) { out = (struct tpm2_get_cap_out *) &buf.data[TPM_HEADER_SIZE]; - *value = be32_to_cpu(out->value); + /* + * To prevent failing boot up of some systems, Infineon TPM2.0 + * returns SUCCESS on TPM2_Startup in field upgrade mode. Also + * the TPM2_Getcapability command returns a zero length list + * in field upgrade mode. + */ + if (be32_to_cpu(out->property_cnt) > 0) + *value = be32_to_cpu(out->value); + else + rc = -ENODATA; } tpm_buf_destroy(&buf); return rc; @@ -745,7 +754,11 @@ int tpm2_auto_startup(struct tpm_chip *chip) rc = tpm2_get_cc_attrs_tbl(chip); out: - if (rc == TPM2_RC_UPGRADE) { + /* + * Infineon TPM in field upgrade mode will return no data for the number + * of supported commands. + */ + if (rc == TPM2_RC_UPGRADE || rc == -ENODATA) { dev_info(&chip->dev, "TPM in field upgrade mode, requires firmware upgrade\n"); chip->flags |= TPM_CHIP_FLAG_FIRMWARE_UPGRADE; rc = 0; diff --git a/drivers/char/tpm/tpm_ftpm_tee.c b/drivers/char/tpm/tpm_ftpm_tee.c index 6e3235565a4d..5c233423c56f 100644 --- a/drivers/char/tpm/tpm_ftpm_tee.c +++ b/drivers/char/tpm/tpm_ftpm_tee.c @@ -177,7 +177,7 @@ static u8 ftpm_tee_tpm_op_status(struct tpm_chip *chip) static bool ftpm_tee_tpm_req_canceled(struct tpm_chip *chip, u8 status) { - return 0; + return false; } static const struct tpm_class_ops ftpm_tee_tpm_ops = { diff --git a/drivers/char/tpm/tpm_ibmvtpm.c b/drivers/char/tpm/tpm_ibmvtpm.c index 3af4c07a9342..d3989b257f42 100644 --- a/drivers/char/tpm/tpm_ibmvtpm.c +++ b/drivers/char/tpm/tpm_ibmvtpm.c @@ -681,6 +681,7 @@ static int tpm_ibmvtpm_probe(struct vio_dev *vio_dev, if (!wait_event_timeout(ibmvtpm->crq_queue.wq, ibmvtpm->rtce_buf != NULL, HZ)) { + rc = -ENODEV; dev_err(dev, "CRQ response timed out\n"); goto init_irq_cleanup; } diff --git a/drivers/char/tpm/tpm_tis.c b/drivers/char/tpm/tpm_tis.c index d3f2e5364c27..bcff6429e0b4 100644 --- a/drivers/char/tpm/tpm_tis.c +++ b/drivers/char/tpm/tpm_tis.c @@ -153,50 +153,46 @@ static int check_acpi_tpm2(struct device *dev) #endif static int tpm_tcg_read_bytes(struct tpm_tis_data *data, u32 addr, u16 len, - u8 *result) + u8 *result, enum tpm_tis_io_mode io_mode) { struct tpm_tis_tcg_phy *phy = to_tpm_tis_tcg_phy(data); + __le16 result_le16; + __le32 result_le32; - while (len--) - *result++ = ioread8(phy->iobase + addr); + switch (io_mode) { + case TPM_TIS_PHYS_8: + while (len--) + *result++ = ioread8(phy->iobase + addr); + break; + case TPM_TIS_PHYS_16: + result_le16 = cpu_to_le16(ioread16(phy->iobase + addr)); + memcpy(result, &result_le16, sizeof(u16)); + break; + case TPM_TIS_PHYS_32: + result_le32 = cpu_to_le32(ioread32(phy->iobase + addr)); + memcpy(result, &result_le32, sizeof(u32)); + break; + } return 0; } static int tpm_tcg_write_bytes(struct tpm_tis_data *data, u32 addr, u16 len, - const u8 *value) + const u8 *value, enum tpm_tis_io_mode io_mode) { struct tpm_tis_tcg_phy *phy = to_tpm_tis_tcg_phy(data); - while (len--) - iowrite8(*value++, phy->iobase + addr); - - return 0; -} - -static int tpm_tcg_read16(struct tpm_tis_data *data, u32 addr, u16 *result) -{ - struct tpm_tis_tcg_phy *phy = to_tpm_tis_tcg_phy(data); - - *result = ioread16(phy->iobase + addr); - - return 0; -} - -static int tpm_tcg_read32(struct tpm_tis_data *data, u32 addr, u32 *result) -{ - struct tpm_tis_tcg_phy *phy = to_tpm_tis_tcg_phy(data); - - *result = ioread32(phy->iobase + addr); - - return 0; -} - -static int tpm_tcg_write32(struct tpm_tis_data *data, u32 addr, u32 value) -{ - struct tpm_tis_tcg_phy *phy = to_tpm_tis_tcg_phy(data); - - iowrite32(value, phy->iobase + addr); + switch (io_mode) { + case TPM_TIS_PHYS_8: + while (len--) + iowrite8(*value++, phy->iobase + addr); + break; + case TPM_TIS_PHYS_16: + return -EINVAL; + case TPM_TIS_PHYS_32: + iowrite32(le32_to_cpu(*((__le32 *)value)), phy->iobase + addr); + break; + } return 0; } @@ -204,9 +200,6 @@ static int tpm_tcg_write32(struct tpm_tis_data *data, u32 addr, u32 value) static const struct tpm_tis_phy_ops tpm_tcg = { .read_bytes = tpm_tcg_read_bytes, .write_bytes = tpm_tcg_write_bytes, - .read16 = tpm_tcg_read16, - .read32 = tpm_tcg_read32, - .write32 = tpm_tcg_write32, }; static int tpm_tis_init(struct device *dev, struct tpm_info *tpm_info) diff --git a/drivers/char/tpm/tpm_tis_core.h b/drivers/char/tpm/tpm_tis_core.h index 3be24f221e32..6c203f36b8a1 100644 --- a/drivers/char/tpm/tpm_tis_core.h +++ b/drivers/char/tpm/tpm_tis_core.h @@ -104,54 +104,88 @@ struct tpm_tis_data { unsigned int timeout_max; /* usecs */ }; +/* + * IO modes to indicate how many bytes should be read/written at once in the + * tpm_tis_phy_ops read_bytes/write_bytes calls. Use TPM_TIS_PHYS_8 to + * receive/transmit byte-wise, TPM_TIS_PHYS_16 for two bytes etc. + */ +enum tpm_tis_io_mode { + TPM_TIS_PHYS_8, + TPM_TIS_PHYS_16, + TPM_TIS_PHYS_32, +}; + struct tpm_tis_phy_ops { + /* data is passed in little endian */ int (*read_bytes)(struct tpm_tis_data *data, u32 addr, u16 len, - u8 *result); + u8 *result, enum tpm_tis_io_mode mode); int (*write_bytes)(struct tpm_tis_data *data, u32 addr, u16 len, - const u8 *value); - int (*read16)(struct tpm_tis_data *data, u32 addr, u16 *result); - int (*read32)(struct tpm_tis_data *data, u32 addr, u32 *result); - int (*write32)(struct tpm_tis_data *data, u32 addr, u32 src); + const u8 *value, enum tpm_tis_io_mode mode); }; static inline int tpm_tis_read_bytes(struct tpm_tis_data *data, u32 addr, u16 len, u8 *result) { - return data->phy_ops->read_bytes(data, addr, len, result); + return data->phy_ops->read_bytes(data, addr, len, result, + TPM_TIS_PHYS_8); } static inline int tpm_tis_read8(struct tpm_tis_data *data, u32 addr, u8 *result) { - return data->phy_ops->read_bytes(data, addr, 1, result); + return data->phy_ops->read_bytes(data, addr, 1, result, TPM_TIS_PHYS_8); } static inline int tpm_tis_read16(struct tpm_tis_data *data, u32 addr, u16 *result) { - return data->phy_ops->read16(data, addr, result); + __le16 result_le; + int rc; + + rc = data->phy_ops->read_bytes(data, addr, sizeof(u16), + (u8 *)&result_le, TPM_TIS_PHYS_16); + if (!rc) + *result = le16_to_cpu(result_le); + + return rc; } static inline int tpm_tis_read32(struct tpm_tis_data *data, u32 addr, u32 *result) { - return data->phy_ops->read32(data, addr, result); + __le32 result_le; + int rc; + + rc = data->phy_ops->read_bytes(data, addr, sizeof(u32), + (u8 *)&result_le, TPM_TIS_PHYS_32); + if (!rc) + *result = le32_to_cpu(result_le); + + return rc; } static inline int tpm_tis_write_bytes(struct tpm_tis_data *data, u32 addr, u16 len, const u8 *value) { - return data->phy_ops->write_bytes(data, addr, len, value); + return data->phy_ops->write_bytes(data, addr, len, value, + TPM_TIS_PHYS_8); } static inline int tpm_tis_write8(struct tpm_tis_data *data, u32 addr, u8 value) { - return data->phy_ops->write_bytes(data, addr, 1, &value); + return data->phy_ops->write_bytes(data, addr, 1, &value, + TPM_TIS_PHYS_8); } static inline int tpm_tis_write32(struct tpm_tis_data *data, u32 addr, u32 value) { - return data->phy_ops->write32(data, addr, value); + __le32 value_le; + int rc; + + value_le = cpu_to_le32(value); + rc = data->phy_ops->write_bytes(data, addr, sizeof(u32), + (u8 *)&value_le, TPM_TIS_PHYS_32); + return rc; } static inline bool is_bsw(void) diff --git a/drivers/char/tpm/tpm_tis_i2c_cr50.c b/drivers/char/tpm/tpm_tis_i2c_cr50.c index f6c0affbb456..974479a1ec5a 100644 --- a/drivers/char/tpm/tpm_tis_i2c_cr50.c +++ b/drivers/char/tpm/tpm_tis_i2c_cr50.c @@ -31,6 +31,7 @@ #define TPM_CR50_TIMEOUT_SHORT_MS 2 /* Short timeout during transactions */ #define TPM_CR50_TIMEOUT_NOIRQ_MS 20 /* Timeout for TPM ready without IRQ */ #define TPM_CR50_I2C_DID_VID 0x00281ae0L /* Device and vendor ID reg value */ +#define TPM_TI50_I2C_DID_VID 0x504a6666L /* Device and vendor ID reg value */ #define TPM_CR50_I2C_MAX_RETRIES 3 /* Max retries due to I2C errors */ #define TPM_CR50_I2C_RETRY_DELAY_LO 55 /* Min usecs between retries on I2C */ #define TPM_CR50_I2C_RETRY_DELAY_HI 65 /* Max usecs between retries on I2C */ @@ -742,15 +743,15 @@ static int tpm_cr50_i2c_probe(struct i2c_client *client) } vendor = le32_to_cpup((__le32 *)buf); - if (vendor != TPM_CR50_I2C_DID_VID) { + if (vendor != TPM_CR50_I2C_DID_VID && vendor != TPM_TI50_I2C_DID_VID) { dev_err(dev, "Vendor ID did not match! ID was %08x\n", vendor); tpm_cr50_release_locality(chip, true); return -ENODEV; } - dev_info(dev, "cr50 TPM 2.0 (i2c 0x%02x irq %d id 0x%x)\n", + dev_info(dev, "%s TPM 2.0 (i2c 0x%02x irq %d id 0x%x)\n", + vendor == TPM_TI50_I2C_DID_VID ? "ti50" : "cr50", client->addr, client->irq, vendor >> 16); - return tpm_chip_register(chip); } @@ -768,8 +769,8 @@ static int tpm_cr50_i2c_remove(struct i2c_client *client) struct device *dev = &client->dev; if (!chip) { - dev_err(dev, "Could not get client data at remove\n"); - return -ENODEV; + dev_crit(dev, "Could not get client data at remove, memory corruption ahead\n"); + return 0; } tpm_chip_unregister(chip); diff --git a/drivers/char/tpm/tpm_tis_spi.h b/drivers/char/tpm/tpm_tis_spi.h index bba73979c368..d0f66f6f1931 100644 --- a/drivers/char/tpm/tpm_tis_spi.h +++ b/drivers/char/tpm/tpm_tis_spi.h @@ -31,10 +31,6 @@ extern int tpm_tis_spi_init(struct spi_device *spi, struct tpm_tis_spi_phy *phy, extern int tpm_tis_spi_transfer(struct tpm_tis_data *data, u32 addr, u16 len, u8 *in, const u8 *out); -extern int tpm_tis_spi_read16(struct tpm_tis_data *data, u32 addr, u16 *result); -extern int tpm_tis_spi_read32(struct tpm_tis_data *data, u32 addr, u32 *result); -extern int tpm_tis_spi_write32(struct tpm_tis_data *data, u32 addr, u32 value); - #ifdef CONFIG_TCG_TIS_SPI_CR50 extern int cr50_spi_probe(struct spi_device *spi); #else diff --git a/drivers/char/tpm/tpm_tis_spi_cr50.c b/drivers/char/tpm/tpm_tis_spi_cr50.c index 7bf123d3c537..f4937280e940 100644 --- a/drivers/char/tpm/tpm_tis_spi_cr50.c +++ b/drivers/char/tpm/tpm_tis_spi_cr50.c @@ -222,13 +222,13 @@ static int tpm_tis_spi_cr50_transfer(struct tpm_tis_data *data, u32 addr, u16 le } static int tpm_tis_spi_cr50_read_bytes(struct tpm_tis_data *data, u32 addr, - u16 len, u8 *result) + u16 len, u8 *result, enum tpm_tis_io_mode io_mode) { return tpm_tis_spi_cr50_transfer(data, addr, len, result, NULL); } static int tpm_tis_spi_cr50_write_bytes(struct tpm_tis_data *data, u32 addr, - u16 len, const u8 *value) + u16 len, const u8 *value, enum tpm_tis_io_mode io_mode) { return tpm_tis_spi_cr50_transfer(data, addr, len, NULL, value); } @@ -236,9 +236,6 @@ static int tpm_tis_spi_cr50_write_bytes(struct tpm_tis_data *data, u32 addr, static const struct tpm_tis_phy_ops tpm_spi_cr50_phy_ops = { .read_bytes = tpm_tis_spi_cr50_read_bytes, .write_bytes = tpm_tis_spi_cr50_write_bytes, - .read16 = tpm_tis_spi_read16, - .read32 = tpm_tis_spi_read32, - .write32 = tpm_tis_spi_write32, }; static void cr50_print_fw_version(struct tpm_tis_data *data) diff --git a/drivers/char/tpm/tpm_tis_spi_main.c b/drivers/char/tpm/tpm_tis_spi_main.c index 184396b3af50..a0963a3e92bd 100644 --- a/drivers/char/tpm/tpm_tis_spi_main.c +++ b/drivers/char/tpm/tpm_tis_spi_main.c @@ -141,55 +141,17 @@ exit: } static int tpm_tis_spi_read_bytes(struct tpm_tis_data *data, u32 addr, - u16 len, u8 *result) + u16 len, u8 *result, enum tpm_tis_io_mode io_mode) { return tpm_tis_spi_transfer(data, addr, len, result, NULL); } static int tpm_tis_spi_write_bytes(struct tpm_tis_data *data, u32 addr, - u16 len, const u8 *value) + u16 len, const u8 *value, enum tpm_tis_io_mode io_mode) { return tpm_tis_spi_transfer(data, addr, len, NULL, value); } -int tpm_tis_spi_read16(struct tpm_tis_data *data, u32 addr, u16 *result) -{ - __le16 result_le; - int rc; - - rc = data->phy_ops->read_bytes(data, addr, sizeof(u16), - (u8 *)&result_le); - if (!rc) - *result = le16_to_cpu(result_le); - - return rc; -} - -int tpm_tis_spi_read32(struct tpm_tis_data *data, u32 addr, u32 *result) -{ - __le32 result_le; - int rc; - - rc = data->phy_ops->read_bytes(data, addr, sizeof(u32), - (u8 *)&result_le); - if (!rc) - *result = le32_to_cpu(result_le); - - return rc; -} - -int tpm_tis_spi_write32(struct tpm_tis_data *data, u32 addr, u32 value) -{ - __le32 value_le; - int rc; - - value_le = cpu_to_le32(value); - rc = data->phy_ops->write_bytes(data, addr, sizeof(u32), - (u8 *)&value_le); - - return rc; -} - int tpm_tis_spi_init(struct spi_device *spi, struct tpm_tis_spi_phy *phy, int irq, const struct tpm_tis_phy_ops *phy_ops) { @@ -205,9 +167,6 @@ int tpm_tis_spi_init(struct spi_device *spi, struct tpm_tis_spi_phy *phy, static const struct tpm_tis_phy_ops tpm_spi_phy_ops = { .read_bytes = tpm_tis_spi_read_bytes, .write_bytes = tpm_tis_spi_write_bytes, - .read16 = tpm_tis_spi_read16, - .read32 = tpm_tis_spi_read32, - .write32 = tpm_tis_spi_write32, }; static int tpm_tis_spi_probe(struct spi_device *dev) diff --git a/drivers/char/tpm/tpm_tis_synquacer.c b/drivers/char/tpm/tpm_tis_synquacer.c index e47bdd272704..679196c61401 100644 --- a/drivers/char/tpm/tpm_tis_synquacer.c +++ b/drivers/char/tpm/tpm_tis_synquacer.c @@ -35,72 +35,53 @@ static inline struct tpm_tis_synquacer_phy *to_tpm_tis_tcg_phy(struct tpm_tis_da } static int tpm_tis_synquacer_read_bytes(struct tpm_tis_data *data, u32 addr, - u16 len, u8 *result) + u16 len, u8 *result, + enum tpm_tis_io_mode io_mode) { struct tpm_tis_synquacer_phy *phy = to_tpm_tis_tcg_phy(data); - - while (len--) - *result++ = ioread8(phy->iobase + addr); + switch (io_mode) { + case TPM_TIS_PHYS_8: + while (len--) + *result++ = ioread8(phy->iobase + addr); + break; + case TPM_TIS_PHYS_16: + result[1] = ioread8(phy->iobase + addr + 1); + result[0] = ioread8(phy->iobase + addr); + break; + case TPM_TIS_PHYS_32: + result[3] = ioread8(phy->iobase + addr + 3); + result[2] = ioread8(phy->iobase + addr + 2); + result[1] = ioread8(phy->iobase + addr + 1); + result[0] = ioread8(phy->iobase + addr); + break; + } return 0; } static int tpm_tis_synquacer_write_bytes(struct tpm_tis_data *data, u32 addr, - u16 len, const u8 *value) + u16 len, const u8 *value, + enum tpm_tis_io_mode io_mode) { struct tpm_tis_synquacer_phy *phy = to_tpm_tis_tcg_phy(data); - - while (len--) - iowrite8(*value++, phy->iobase + addr); - - return 0; -} - -static int tpm_tis_synquacer_read16_bw(struct tpm_tis_data *data, - u32 addr, u16 *result) -{ - struct tpm_tis_synquacer_phy *phy = to_tpm_tis_tcg_phy(data); - - /* - * Due to the limitation of SPI controller on SynQuacer, - * 16/32 bits access must be done in byte-wise and descending order. - */ - *result = (ioread8(phy->iobase + addr + 1) << 8) | - (ioread8(phy->iobase + addr)); - - return 0; -} - -static int tpm_tis_synquacer_read32_bw(struct tpm_tis_data *data, - u32 addr, u32 *result) -{ - struct tpm_tis_synquacer_phy *phy = to_tpm_tis_tcg_phy(data); - - /* - * Due to the limitation of SPI controller on SynQuacer, - * 16/32 bits access must be done in byte-wise and descending order. - */ - *result = (ioread8(phy->iobase + addr + 3) << 24) | - (ioread8(phy->iobase + addr + 2) << 16) | - (ioread8(phy->iobase + addr + 1) << 8) | - (ioread8(phy->iobase + addr)); - - return 0; -} - -static int tpm_tis_synquacer_write32_bw(struct tpm_tis_data *data, - u32 addr, u32 value) -{ - struct tpm_tis_synquacer_phy *phy = to_tpm_tis_tcg_phy(data); - - /* - * Due to the limitation of SPI controller on SynQuacer, - * 16/32 bits access must be done in byte-wise and descending order. - */ - iowrite8(value >> 24, phy->iobase + addr + 3); - iowrite8(value >> 16, phy->iobase + addr + 2); - iowrite8(value >> 8, phy->iobase + addr + 1); - iowrite8(value, phy->iobase + addr); + switch (io_mode) { + case TPM_TIS_PHYS_8: + while (len--) + iowrite8(*value++, phy->iobase + addr); + break; + case TPM_TIS_PHYS_16: + return -EINVAL; + case TPM_TIS_PHYS_32: + /* + * Due to the limitation of SPI controller on SynQuacer, + * 16/32 bits access must be done in byte-wise and descending order. + */ + iowrite8(value[3], phy->iobase + addr + 3); + iowrite8(value[2], phy->iobase + addr + 2); + iowrite8(value[1], phy->iobase + addr + 1); + iowrite8(value[0], phy->iobase + addr); + break; + } return 0; } @@ -108,9 +89,6 @@ static int tpm_tis_synquacer_write32_bw(struct tpm_tis_data *data, static const struct tpm_tis_phy_ops tpm_tcg_bw = { .read_bytes = tpm_tis_synquacer_read_bytes, .write_bytes = tpm_tis_synquacer_write_bytes, - .read16 = tpm_tis_synquacer_read16_bw, - .read32 = tpm_tis_synquacer_read32_bw, - .write32 = tpm_tis_synquacer_write32_bw, }; static int tpm_tis_synquacer_init(struct device *dev, diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index 82d370ae6a4a..d092c9bb4ba3 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -389,6 +389,27 @@ static int cppc_cpufreq_set_target(struct cpufreq_policy *policy, return ret; } +static unsigned int cppc_cpufreq_fast_switch(struct cpufreq_policy *policy, + unsigned int target_freq) +{ + struct cppc_cpudata *cpu_data = policy->driver_data; + unsigned int cpu = policy->cpu; + u32 desired_perf; + int ret; + + desired_perf = cppc_cpufreq_khz_to_perf(cpu_data, target_freq); + cpu_data->perf_ctrls.desired_perf = desired_perf; + ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls); + + if (ret) { + pr_debug("Failed to set target on CPU:%d. ret:%d\n", + cpu, ret); + return 0; + } + + return target_freq; +} + static int cppc_verify_policy(struct cpufreq_policy_data *policy) { cpufreq_verify_within_cpu_limits(policy); @@ -420,12 +441,197 @@ static unsigned int cppc_cpufreq_get_transition_delay_us(unsigned int cpu) return cppc_get_transition_latency(cpu) / NSEC_PER_USEC; } +static DEFINE_PER_CPU(unsigned int, efficiency_class); +static void cppc_cpufreq_register_em(struct cpufreq_policy *policy); + +/* Create an artificial performance state every CPPC_EM_CAP_STEP capacity unit. */ +#define CPPC_EM_CAP_STEP (20) +/* Increase the cost value by CPPC_EM_COST_STEP every performance state. */ +#define CPPC_EM_COST_STEP (1) +/* Add a cost gap correspnding to the energy of 4 CPUs. */ +#define CPPC_EM_COST_GAP (4 * SCHED_CAPACITY_SCALE * CPPC_EM_COST_STEP \ + / CPPC_EM_CAP_STEP) + +static unsigned int get_perf_level_count(struct cpufreq_policy *policy) +{ + struct cppc_perf_caps *perf_caps; + unsigned int min_cap, max_cap; + struct cppc_cpudata *cpu_data; + int cpu = policy->cpu; + + cpu_data = policy->driver_data; + perf_caps = &cpu_data->perf_caps; + max_cap = arch_scale_cpu_capacity(cpu); + min_cap = div_u64(max_cap * perf_caps->lowest_perf, perf_caps->highest_perf); + if ((min_cap == 0) || (max_cap < min_cap)) + return 0; + return 1 + max_cap / CPPC_EM_CAP_STEP - min_cap / CPPC_EM_CAP_STEP; +} + +/* + * The cost is defined as: + * cost = power * max_frequency / frequency + */ +static inline unsigned long compute_cost(int cpu, int step) +{ + return CPPC_EM_COST_GAP * per_cpu(efficiency_class, cpu) + + step * CPPC_EM_COST_STEP; +} + +static int cppc_get_cpu_power(struct device *cpu_dev, + unsigned long *power, unsigned long *KHz) +{ + unsigned long perf_step, perf_prev, perf, perf_check; + unsigned int min_step, max_step, step, step_check; + unsigned long prev_freq = *KHz; + unsigned int min_cap, max_cap; + struct cpufreq_policy *policy; + + struct cppc_perf_caps *perf_caps; + struct cppc_cpudata *cpu_data; + + policy = cpufreq_cpu_get_raw(cpu_dev->id); + cpu_data = policy->driver_data; + perf_caps = &cpu_data->perf_caps; + max_cap = arch_scale_cpu_capacity(cpu_dev->id); + min_cap = div_u64(max_cap * perf_caps->lowest_perf, + perf_caps->highest_perf); + + perf_step = CPPC_EM_CAP_STEP * perf_caps->highest_perf / max_cap; + min_step = min_cap / CPPC_EM_CAP_STEP; + max_step = max_cap / CPPC_EM_CAP_STEP; + + perf_prev = cppc_cpufreq_khz_to_perf(cpu_data, *KHz); + step = perf_prev / perf_step; + + if (step > max_step) + return -EINVAL; + + if (min_step == max_step) { + step = max_step; + perf = perf_caps->highest_perf; + } else if (step < min_step) { + step = min_step; + perf = perf_caps->lowest_perf; + } else { + step++; + if (step == max_step) + perf = perf_caps->highest_perf; + else + perf = step * perf_step; + } + + *KHz = cppc_cpufreq_perf_to_khz(cpu_data, perf); + perf_check = cppc_cpufreq_khz_to_perf(cpu_data, *KHz); + step_check = perf_check / perf_step; + + /* + * To avoid bad integer approximation, check that new frequency value + * increased and that the new frequency will be converted to the + * desired step value. + */ + while ((*KHz == prev_freq) || (step_check != step)) { + perf++; + *KHz = cppc_cpufreq_perf_to_khz(cpu_data, perf); + perf_check = cppc_cpufreq_khz_to_perf(cpu_data, *KHz); + step_check = perf_check / perf_step; + } + + /* + * With an artificial EM, only the cost value is used. Still the power + * is populated such as 0 < power < EM_MAX_POWER. This allows to add + * more sense to the artificial performance states. + */ + *power = compute_cost(cpu_dev->id, step); + + return 0; +} + +static int cppc_get_cpu_cost(struct device *cpu_dev, unsigned long KHz, + unsigned long *cost) +{ + unsigned long perf_step, perf_prev; + struct cppc_perf_caps *perf_caps; + struct cpufreq_policy *policy; + struct cppc_cpudata *cpu_data; + unsigned int max_cap; + int step; + + policy = cpufreq_cpu_get_raw(cpu_dev->id); + cpu_data = policy->driver_data; + perf_caps = &cpu_data->perf_caps; + max_cap = arch_scale_cpu_capacity(cpu_dev->id); + + perf_prev = cppc_cpufreq_khz_to_perf(cpu_data, KHz); + perf_step = CPPC_EM_CAP_STEP * perf_caps->highest_perf / max_cap; + step = perf_prev / perf_step; + + *cost = compute_cost(cpu_dev->id, step); + + return 0; +} + +static int populate_efficiency_class(void) +{ + struct acpi_madt_generic_interrupt *gicc; + DECLARE_BITMAP(used_classes, 256) = {}; + int class, cpu, index; + + for_each_possible_cpu(cpu) { + gicc = acpi_cpu_get_madt_gicc(cpu); + class = gicc->efficiency_class; + bitmap_set(used_classes, class, 1); + } + + if (bitmap_weight(used_classes, 256) <= 1) { + pr_debug("Efficiency classes are all equal (=%d). " + "No EM registered", class); + return -EINVAL; + } + + /* + * Squeeze efficiency class values on [0:#efficiency_class-1]. + * Values are per spec in [0:255]. + */ + index = 0; + for_each_set_bit(class, used_classes, 256) { + for_each_possible_cpu(cpu) { + gicc = acpi_cpu_get_madt_gicc(cpu); + if (gicc->efficiency_class == class) + per_cpu(efficiency_class, cpu) = index; + } + index++; + } + cppc_cpufreq_driver.register_em = cppc_cpufreq_register_em; + + return 0; +} + +static void cppc_cpufreq_register_em(struct cpufreq_policy *policy) +{ + struct cppc_cpudata *cpu_data; + struct em_data_callback em_cb = + EM_ADV_DATA_CB(cppc_get_cpu_power, cppc_get_cpu_cost); + + cpu_data = policy->driver_data; + em_dev_register_perf_domain(get_cpu_device(policy->cpu), + get_perf_level_count(policy), &em_cb, + cpu_data->shared_cpu_map, 0); +} + #else static unsigned int cppc_cpufreq_get_transition_delay_us(unsigned int cpu) { return cppc_get_transition_latency(cpu) / NSEC_PER_USEC; } +static int populate_efficiency_class(void) +{ + return 0; +} +static void cppc_cpufreq_register_em(struct cpufreq_policy *policy) +{ +} #endif @@ -536,6 +742,9 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy) goto out; } + policy->fast_switch_possible = cppc_allow_fast_switch(); + policy->dvfs_possible_from_any_cpu = true; + /* * If 'highest_perf' is greater than 'nominal_perf', we assume CPU Boost * is supported. @@ -681,6 +890,7 @@ static struct cpufreq_driver cppc_cpufreq_driver = { .verify = cppc_verify_policy, .target = cppc_cpufreq_set_target, .get = cppc_cpufreq_get_rate, + .fast_switch = cppc_cpufreq_fast_switch, .init = cppc_cpufreq_cpu_init, .exit = cppc_cpufreq_cpu_exit, .set_boost = cppc_cpufreq_set_boost, @@ -742,6 +952,7 @@ static int __init cppc_cpufreq_init(void) cppc_check_hisi_workaround(); cppc_freq_invariance_init(); + populate_efficiency_class(); ret = cpufreq_register_driver(&cppc_cpufreq_driver); if (ret) diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 47df0243ce5b..51662d66bb06 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -958,13 +959,14 @@ static ssize_t show(struct kobject *kobj, struct attribute *attr, char *buf) { struct cpufreq_policy *policy = to_policy(kobj); struct freq_attr *fattr = to_attr(attr); - ssize_t ret; + ssize_t ret = -EBUSY; if (!fattr->show) return -EIO; down_read(&policy->rwsem); - ret = fattr->show(policy, buf); + if (likely(!policy_is_inactive(policy))) + ret = fattr->show(policy, buf); up_read(&policy->rwsem); return ret; @@ -975,7 +977,7 @@ static ssize_t store(struct kobject *kobj, struct attribute *attr, { struct cpufreq_policy *policy = to_policy(kobj); struct freq_attr *fattr = to_attr(attr); - ssize_t ret = -EINVAL; + ssize_t ret = -EBUSY; if (!fattr->store) return -EIO; @@ -989,7 +991,8 @@ static ssize_t store(struct kobject *kobj, struct attribute *attr, if (cpu_online(policy->cpu)) { down_write(&policy->rwsem); - ret = fattr->store(policy, buf, count); + if (likely(!policy_is_inactive(policy))) + ret = fattr->store(policy, buf, count); up_write(&policy->rwsem); } @@ -1030,11 +1033,12 @@ static void add_cpu_dev_symlink(struct cpufreq_policy *policy, unsigned int cpu, dev_err(dev, "cpufreq symlink creation failed\n"); } -static void remove_cpu_dev_symlink(struct cpufreq_policy *policy, +static void remove_cpu_dev_symlink(struct cpufreq_policy *policy, int cpu, struct device *dev) { dev_dbg(dev, "%s: Removing symlink\n", __func__); sysfs_remove_link(&dev->kobj, "cpufreq"); + cpumask_clear_cpu(cpu, policy->real_cpus); } static int cpufreq_add_dev_interface(struct cpufreq_policy *policy) @@ -1348,12 +1352,12 @@ static int cpufreq_online(unsigned int cpu) down_write(&policy->rwsem); policy->cpu = cpu; policy->governor = NULL; - up_write(&policy->rwsem); } else { new_policy = true; policy = cpufreq_policy_alloc(cpu); if (!policy) return -ENOMEM; + down_write(&policy->rwsem); } if (!new_policy && cpufreq_driver->online) { @@ -1393,7 +1397,6 @@ static int cpufreq_online(unsigned int cpu) cpumask_copy(policy->related_cpus, policy->cpus); } - down_write(&policy->rwsem); /* * affected cpus must always be the one, which are online. We aren't * managing offline cpus here. @@ -1543,9 +1546,9 @@ static int cpufreq_online(unsigned int cpu) out_destroy_policy: for_each_cpu(j, policy->real_cpus) - remove_cpu_dev_symlink(policy, get_cpu_device(j)); + remove_cpu_dev_symlink(policy, j, get_cpu_device(j)); - up_write(&policy->rwsem); + cpumask_clear(policy->cpus); out_offline_policy: if (cpufreq_driver->offline) @@ -1556,6 +1559,8 @@ out_exit_policy: cpufreq_driver->exit(policy); out_free_policy: + up_write(&policy->rwsem); + cpufreq_policy_free(policy); return ret; } @@ -1587,47 +1592,36 @@ static int cpufreq_add_dev(struct device *dev, struct subsys_interface *sif) return 0; } -static int cpufreq_offline(unsigned int cpu) +static void __cpufreq_offline(unsigned int cpu, struct cpufreq_policy *policy) { - struct cpufreq_policy *policy; int ret; - pr_debug("%s: unregistering CPU %u\n", __func__, cpu); - - policy = cpufreq_cpu_get_raw(cpu); - if (!policy) { - pr_debug("%s: No cpu_data found\n", __func__); - return 0; - } - - down_write(&policy->rwsem); if (has_target()) cpufreq_stop_governor(policy); cpumask_clear_cpu(cpu, policy->cpus); - if (policy_is_inactive(policy)) { - if (has_target()) - strncpy(policy->last_governor, policy->governor->name, - CPUFREQ_NAME_LEN); - else - policy->last_policy = policy->policy; - } else if (cpu == policy->cpu) { - /* Nominate new CPU */ - policy->cpu = cpumask_any(policy->cpus); - } - - /* Start governor again for active policy */ if (!policy_is_inactive(policy)) { + /* Nominate a new CPU if necessary. */ + if (cpu == policy->cpu) + policy->cpu = cpumask_any(policy->cpus); + + /* Start the governor again for the active policy. */ if (has_target()) { ret = cpufreq_start_governor(policy); if (ret) pr_err("%s: Failed to start governor\n", __func__); } - goto unlock; + return; } + if (has_target()) + strncpy(policy->last_governor, policy->governor->name, + CPUFREQ_NAME_LEN); + else + policy->last_policy = policy->policy; + if (cpufreq_thermal_control_enabled(cpufreq_driver)) { cpufreq_cooling_unregister(policy->cdev); policy->cdev = NULL; @@ -1646,8 +1640,24 @@ static int cpufreq_offline(unsigned int cpu) cpufreq_driver->exit(policy); policy->freq_table = NULL; } +} + +static int cpufreq_offline(unsigned int cpu) +{ + struct cpufreq_policy *policy; + + pr_debug("%s: unregistering CPU %u\n", __func__, cpu); + + policy = cpufreq_cpu_get_raw(cpu); + if (!policy) { + pr_debug("%s: No cpu_data found\n", __func__); + return 0; + } + + down_write(&policy->rwsem); + + __cpufreq_offline(cpu, policy); -unlock: up_write(&policy->rwsem); return 0; } @@ -1665,19 +1675,25 @@ static void cpufreq_remove_dev(struct device *dev, struct subsys_interface *sif) if (!policy) return; + down_write(&policy->rwsem); + if (cpu_online(cpu)) - cpufreq_offline(cpu); + __cpufreq_offline(cpu, policy); - cpumask_clear_cpu(cpu, policy->real_cpus); - remove_cpu_dev_symlink(policy, dev); + remove_cpu_dev_symlink(policy, cpu, dev); - if (cpumask_empty(policy->real_cpus)) { - /* We did light-weight exit earlier, do full tear down now */ - if (cpufreq_driver->offline) - cpufreq_driver->exit(policy); - - cpufreq_policy_free(policy); + if (!cpumask_empty(policy->real_cpus)) { + up_write(&policy->rwsem); + return; } + + /* We did light-weight exit earlier, do full tear down now */ + if (cpufreq_driver->offline) + cpufreq_driver->exit(policy); + + up_write(&policy->rwsem); + + cpufreq_policy_free(policy); } /** @@ -1719,6 +1735,16 @@ static unsigned int cpufreq_verify_current_freq(struct cpufreq_policy *policy, b return new_freq; if (policy->cur != new_freq) { + /* + * For some platforms, the frequency returned by hardware may be + * slightly different from what is provided in the frequency + * table, for example hardware may return 499 MHz instead of 500 + * MHz. In such cases it is better to avoid getting into + * unnecessary frequency updates. + */ + if (abs(policy->cur - new_freq) < HZ_PER_MHZ) + return policy->cur; + cpufreq_out_of_sync(policy, new_freq); if (update) schedule_work(&policy->update); diff --git a/drivers/cpufreq/cpufreq_governor.c b/drivers/cpufreq/cpufreq_governor.c index 0d42cf8b88d8..85da677c43d6 100644 --- a/drivers/cpufreq/cpufreq_governor.c +++ b/drivers/cpufreq/cpufreq_governor.c @@ -388,6 +388,15 @@ static void free_policy_dbs_info(struct policy_dbs_info *policy_dbs, gov->free(policy_dbs); } +static void cpufreq_dbs_data_release(struct kobject *kobj) +{ + struct dbs_data *dbs_data = to_dbs_data(to_gov_attr_set(kobj)); + struct dbs_governor *gov = dbs_data->gov; + + gov->exit(dbs_data); + kfree(dbs_data); +} + int cpufreq_dbs_governor_init(struct cpufreq_policy *policy) { struct dbs_governor *gov = dbs_governor_of(policy); @@ -425,6 +434,7 @@ int cpufreq_dbs_governor_init(struct cpufreq_policy *policy) goto free_policy_dbs_info; } + dbs_data->gov = gov; gov_attr_set_init(&dbs_data->attr_set, &policy_dbs->list); ret = gov->init(dbs_data); @@ -447,6 +457,7 @@ int cpufreq_dbs_governor_init(struct cpufreq_policy *policy) policy->governor_data = policy_dbs; gov->kobj_type.sysfs_ops = &governor_sysfs_ops; + gov->kobj_type.release = cpufreq_dbs_data_release; ret = kobject_init_and_add(&dbs_data->attr_set.kobj, &gov->kobj_type, get_governor_parent_kobj(policy), "%s", gov->gov.name); @@ -488,13 +499,8 @@ void cpufreq_dbs_governor_exit(struct cpufreq_policy *policy) policy->governor_data = NULL; - if (!count) { - if (!have_governor_per_policy()) - gov->gdbs_data = NULL; - - gov->exit(dbs_data); - kfree(dbs_data); - } + if (!count && !have_governor_per_policy()) + gov->gdbs_data = NULL; free_policy_dbs_info(policy_dbs, gov); diff --git a/drivers/cpufreq/cpufreq_governor.h b/drivers/cpufreq/cpufreq_governor.h index a5a0bc3cc23e..168c23fd7fca 100644 --- a/drivers/cpufreq/cpufreq_governor.h +++ b/drivers/cpufreq/cpufreq_governor.h @@ -37,6 +37,7 @@ enum {OD_NORMAL_SAMPLE, OD_SUB_SAMPLE}; /* Governor demand based switching data (per-policy or global). */ struct dbs_data { struct gov_attr_set attr_set; + struct dbs_governor *gov; void *tuners; unsigned int ignore_nice_load; unsigned int sampling_rate; diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index 846bb3a78788..57cdb3679885 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -1322,6 +1322,7 @@ static ssize_t store_no_turbo(struct kobject *a, struct kobj_attribute *b, mutex_unlock(&intel_pstate_limits_lock); intel_pstate_update_policies(); + arch_set_max_freq_ratio(global.no_turbo); mutex_unlock(&intel_pstate_driver_lock); @@ -2424,6 +2425,7 @@ static const struct x86_cpu_id intel_pstate_cpu_oob_ids[] __initconst = { X86_MATCH(BROADWELL_X, core_funcs), X86_MATCH(SKYLAKE_X, core_funcs), X86_MATCH(ICELAKE_X, core_funcs), + X86_MATCH(SAPPHIRERAPIDS_X, core_funcs), {} }; diff --git a/drivers/cpufreq/mediatek-cpufreq-hw.c b/drivers/cpufreq/mediatek-cpufreq-hw.c index 0a94c56ddad2..813cccbfe934 100644 --- a/drivers/cpufreq/mediatek-cpufreq-hw.c +++ b/drivers/cpufreq/mediatek-cpufreq-hw.c @@ -51,8 +51,8 @@ static const u16 cpufreq_mtk_offsets[REG_ARRAY_SIZE] = { }; static int __maybe_unused -mtk_cpufreq_get_cpu_power(unsigned long *mW, - unsigned long *KHz, struct device *cpu_dev) +mtk_cpufreq_get_cpu_power(struct device *cpu_dev, unsigned long *mW, + unsigned long *KHz) { struct mtk_cpufreq_data *data; struct cpufreq_policy *policy; diff --git a/drivers/cpufreq/pasemi-cpufreq.c b/drivers/cpufreq/pasemi-cpufreq.c index 815645170c4d..039a66bbe1be 100644 --- a/drivers/cpufreq/pasemi-cpufreq.c +++ b/drivers/cpufreq/pasemi-cpufreq.c @@ -18,7 +18,6 @@ #include #include -#include #include #include diff --git a/drivers/cpufreq/pmac32-cpufreq.c b/drivers/cpufreq/pmac32-cpufreq.c index 4f20c6a9108d..20f64a8b0a35 100644 --- a/drivers/cpufreq/pmac32-cpufreq.c +++ b/drivers/cpufreq/pmac32-cpufreq.c @@ -24,7 +24,7 @@ #include #include #include -#include + #include #include #include diff --git a/drivers/cpufreq/pmac64-cpufreq.c b/drivers/cpufreq/pmac64-cpufreq.c index d7542a106e6b..ba9c31d98bd6 100644 --- a/drivers/cpufreq/pmac64-cpufreq.c +++ b/drivers/cpufreq/pmac64-cpufreq.c @@ -22,7 +22,7 @@ #include #include #include -#include + #include #include #include diff --git a/drivers/cpufreq/ppc_cbe_cpufreq.c b/drivers/cpufreq/ppc_cbe_cpufreq.c index c58abb4cca3a..e3313ce63b38 100644 --- a/drivers/cpufreq/ppc_cbe_cpufreq.c +++ b/drivers/cpufreq/ppc_cbe_cpufreq.c @@ -12,7 +12,6 @@ #include #include -#include #include #include "ppc_cbe_cpufreq.h" diff --git a/drivers/cpufreq/ppc_cbe_cpufreq_pmi.c b/drivers/cpufreq/ppc_cbe_cpufreq_pmi.c index 037fe23bc6ed..4fba3637b115 100644 --- a/drivers/cpufreq/ppc_cbe_cpufreq_pmi.c +++ b/drivers/cpufreq/ppc_cbe_cpufreq_pmi.c @@ -13,9 +13,9 @@ #include #include #include +#include #include -#include #include #include diff --git a/drivers/cpufreq/scmi-cpufreq.c b/drivers/cpufreq/scmi-cpufreq.c index 919fa6e3f462..6d2a4cf46db7 100644 --- a/drivers/cpufreq/scmi-cpufreq.c +++ b/drivers/cpufreq/scmi-cpufreq.c @@ -96,8 +96,8 @@ scmi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask) } static int __maybe_unused -scmi_get_cpu_power(unsigned long *power, unsigned long *KHz, - struct device *cpu_dev) +scmi_get_cpu_power(struct device *cpu_dev, unsigned long *power, + unsigned long *KHz) { unsigned long Hz; int ret, domain; diff --git a/drivers/cpuidle/cpuidle-psci-domain.c b/drivers/cpuidle/cpuidle-psci-domain.c index 755bbdfc5b82..3db4fca1172b 100644 --- a/drivers/cpuidle/cpuidle-psci-domain.c +++ b/drivers/cpuidle/cpuidle-psci-domain.c @@ -52,7 +52,7 @@ static int psci_pd_init(struct device_node *np, bool use_osi) struct generic_pm_domain *pd; struct psci_pd_provider *pd_provider; struct dev_power_governor *pd_gov; - int ret = -ENOMEM, state_count = 0; + int ret = -ENOMEM; pd = dt_idle_pd_alloc(np, psci_dt_parse_state_node); if (!pd) @@ -71,7 +71,7 @@ static int psci_pd_init(struct device_node *np, bool use_osi) pd->flags |= GENPD_FLAG_ALWAYS_ON; /* Use governor for CPU PM domains if it has some states to manage. */ - pd_gov = state_count > 0 ? &pm_domain_cpu_gov : NULL; + pd_gov = pd->states ? &pm_domain_cpu_gov : NULL; ret = pm_genpd_init(pd, pd_gov, false); if (ret) diff --git a/drivers/cpuidle/cpuidle-psci.c b/drivers/cpuidle/cpuidle-psci.c index efc063ffc683..85e66d9fb841 100644 --- a/drivers/cpuidle/cpuidle-psci.c +++ b/drivers/cpuidle/cpuidle-psci.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -136,6 +137,49 @@ static int psci_idle_cpuhp_down(unsigned int cpu) return 0; } +static void psci_idle_syscore_switch(bool suspend) +{ + bool cleared = false; + struct device *dev; + int cpu; + + for_each_possible_cpu(cpu) { + dev = per_cpu_ptr(&psci_cpuidle_data, cpu)->dev; + + if (dev && suspend) { + dev_pm_genpd_suspend(dev); + } else if (dev) { + dev_pm_genpd_resume(dev); + + /* Account for userspace having offlined a CPU. */ + if (pm_runtime_status_suspended(dev)) + pm_runtime_set_active(dev); + + /* Clear domain state to re-start fresh. */ + if (!cleared) { + psci_set_domain_state(0); + cleared = true; + } + } + } +} + +static int psci_idle_syscore_suspend(void) +{ + psci_idle_syscore_switch(true); + return 0; +} + +static void psci_idle_syscore_resume(void) +{ + psci_idle_syscore_switch(false); +} + +static struct syscore_ops psci_idle_syscore_ops = { + .suspend = psci_idle_syscore_suspend, + .resume = psci_idle_syscore_resume, +}; + static void psci_idle_init_cpuhp(void) { int err; @@ -143,6 +187,8 @@ static void psci_idle_init_cpuhp(void) if (!psci_cpuidle_use_cpuhp) return; + register_syscore_ops(&psci_idle_syscore_ops); + err = cpuhp_setup_state_nocalls(CPUHP_AP_CPU_PM_STARTING, "cpuidle/psci:online", psci_idle_cpuhp_up, diff --git a/drivers/cpuidle/cpuidle-riscv-sbi.c b/drivers/cpuidle/cpuidle-riscv-sbi.c index 5c852e671992..1151e5e2ba82 100644 --- a/drivers/cpuidle/cpuidle-riscv-sbi.c +++ b/drivers/cpuidle/cpuidle-riscv-sbi.c @@ -414,7 +414,7 @@ static int sbi_pd_init(struct device_node *np) struct generic_pm_domain *pd; struct sbi_pd_provider *pd_provider; struct dev_power_governor *pd_gov; - int ret = -ENOMEM, state_count = 0; + int ret = -ENOMEM; pd = dt_idle_pd_alloc(np, sbi_dt_parse_state_node); if (!pd) @@ -433,7 +433,7 @@ static int sbi_pd_init(struct device_node *np) pd->flags |= GENPD_FLAG_ALWAYS_ON; /* Use governor for CPU PM domains if it has some states to manage. */ - pd_gov = state_count > 0 ? &pm_domain_cpu_gov : NULL; + pd_gov = pd->states ? &pm_domain_cpu_gov : NULL; ret = pm_genpd_init(pd, pd_gov, false); if (ret) diff --git a/drivers/crypto/caam/Kconfig b/drivers/crypto/caam/Kconfig index 84ea7cba5ee5..ea9f8b1ae981 100644 --- a/drivers/crypto/caam/Kconfig +++ b/drivers/crypto/caam/Kconfig @@ -151,6 +151,9 @@ config CRYPTO_DEV_FSL_CAAM_RNG_API Selecting this will register the SEC4 hardware rng to the hw_random API for supplying the kernel entropy pool. +config CRYPTO_DEV_FSL_CAAM_BLOB_GEN + bool + endif # CRYPTO_DEV_FSL_CAAM_JR endif # CRYPTO_DEV_FSL_CAAM diff --git a/drivers/crypto/caam/Makefile b/drivers/crypto/caam/Makefile index 3570286eb9ce..25f7ae5a4642 100644 --- a/drivers/crypto/caam/Makefile +++ b/drivers/crypto/caam/Makefile @@ -21,6 +21,7 @@ caam_jr-$(CONFIG_CRYPTO_DEV_FSL_CAAM_CRYPTO_API_QI) += caamalg_qi.o caam_jr-$(CONFIG_CRYPTO_DEV_FSL_CAAM_AHASH_API) += caamhash.o caam_jr-$(CONFIG_CRYPTO_DEV_FSL_CAAM_RNG_API) += caamrng.o caam_jr-$(CONFIG_CRYPTO_DEV_FSL_CAAM_PKC_API) += caampkc.o pkc_desc.o +caam_jr-$(CONFIG_CRYPTO_DEV_FSL_CAAM_BLOB_GEN) += blob_gen.o caam-$(CONFIG_CRYPTO_DEV_FSL_CAAM_CRYPTO_API_QI) += qi.o ifneq ($(CONFIG_CRYPTO_DEV_FSL_CAAM_CRYPTO_API_QI),) diff --git a/drivers/crypto/caam/blob_gen.c b/drivers/crypto/caam/blob_gen.c new file mode 100644 index 000000000000..6345c7269eb0 --- /dev/null +++ b/drivers/crypto/caam/blob_gen.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 Pengutronix, Steffen Trumtrar + * Copyright (C) 2021 Pengutronix, Ahmad Fatoum + */ + +#define pr_fmt(fmt) "caam blob_gen: " fmt + +#include +#include + +#include "compat.h" +#include "desc_constr.h" +#include "desc.h" +#include "error.h" +#include "intern.h" +#include "jr.h" +#include "regs.h" + +#define CAAM_BLOB_DESC_BYTES_MAX \ + /* Command to initialize & stating length of descriptor */ \ + (CAAM_CMD_SZ + \ + /* Command to append the key-modifier + key-modifier data */ \ + CAAM_CMD_SZ + CAAM_BLOB_KEYMOD_LENGTH + \ + /* Command to include input key + pointer to the input key */ \ + CAAM_CMD_SZ + CAAM_PTR_SZ_MAX + \ + /* Command to include output key + pointer to the output key */ \ + CAAM_CMD_SZ + CAAM_PTR_SZ_MAX + \ + /* Command describing the operation to perform */ \ + CAAM_CMD_SZ) + +struct caam_blob_priv { + struct device jrdev; +}; + +struct caam_blob_job_result { + int err; + struct completion completion; +}; + +static void caam_blob_job_done(struct device *dev, u32 *desc, u32 err, void *context) +{ + struct caam_blob_job_result *res = context; + int ecode = 0; + + dev_dbg(dev, "%s %d: err 0x%x\n", __func__, __LINE__, err); + + if (err) + ecode = caam_jr_strstatus(dev, err); + + res->err = ecode; + + /* + * Upon completion, desc points to a buffer containing a CAAM job + * descriptor which encapsulates data into an externally-storable + * blob. + */ + complete(&res->completion); +} + +int caam_process_blob(struct caam_blob_priv *priv, + struct caam_blob_info *info, bool encap) +{ + struct caam_blob_job_result testres; + struct device *jrdev = &priv->jrdev; + dma_addr_t dma_in, dma_out; + int op = OP_PCLID_BLOB; + size_t output_len; + u32 *desc; + int ret; + + if (info->key_mod_len > CAAM_BLOB_KEYMOD_LENGTH) + return -EINVAL; + + if (encap) { + op |= OP_TYPE_ENCAP_PROTOCOL; + output_len = info->input_len + CAAM_BLOB_OVERHEAD; + } else { + op |= OP_TYPE_DECAP_PROTOCOL; + output_len = info->input_len - CAAM_BLOB_OVERHEAD; + } + + desc = kzalloc(CAAM_BLOB_DESC_BYTES_MAX, GFP_KERNEL | GFP_DMA); + if (!desc) + return -ENOMEM; + + dma_in = dma_map_single(jrdev, info->input, info->input_len, + DMA_TO_DEVICE); + if (dma_mapping_error(jrdev, dma_in)) { + dev_err(jrdev, "unable to map input DMA buffer\n"); + ret = -ENOMEM; + goto out_free; + } + + dma_out = dma_map_single(jrdev, info->output, output_len, + DMA_FROM_DEVICE); + if (dma_mapping_error(jrdev, dma_out)) { + dev_err(jrdev, "unable to map output DMA buffer\n"); + ret = -ENOMEM; + goto out_unmap_in; + } + + /* + * A data blob is encrypted using a blob key (BK); a random number. + * The BK is used as an AES-CCM key. The initial block (B0) and the + * initial counter (Ctr0) are generated automatically and stored in + * Class 1 Context DWords 0+1+2+3. The random BK is stored in the + * Class 1 Key Register. Operation Mode is set to AES-CCM. + */ + + init_job_desc(desc, 0); + append_key_as_imm(desc, info->key_mod, info->key_mod_len, + info->key_mod_len, CLASS_2 | KEY_DEST_CLASS_REG); + append_seq_in_ptr_intlen(desc, dma_in, info->input_len, 0); + append_seq_out_ptr_intlen(desc, dma_out, output_len, 0); + append_operation(desc, op); + + print_hex_dump_debug("data@"__stringify(__LINE__)": ", + DUMP_PREFIX_ADDRESS, 16, 1, info->input, + info->input_len, false); + print_hex_dump_debug("jobdesc@"__stringify(__LINE__)": ", + DUMP_PREFIX_ADDRESS, 16, 1, desc, + desc_bytes(desc), false); + + testres.err = 0; + init_completion(&testres.completion); + + ret = caam_jr_enqueue(jrdev, desc, caam_blob_job_done, &testres); + if (ret == -EINPROGRESS) { + wait_for_completion(&testres.completion); + ret = testres.err; + print_hex_dump_debug("output@"__stringify(__LINE__)": ", + DUMP_PREFIX_ADDRESS, 16, 1, info->output, + output_len, false); + } + + if (ret == 0) + info->output_len = output_len; + + dma_unmap_single(jrdev, dma_out, output_len, DMA_FROM_DEVICE); +out_unmap_in: + dma_unmap_single(jrdev, dma_in, info->input_len, DMA_TO_DEVICE); +out_free: + kfree(desc); + + return ret; +} +EXPORT_SYMBOL(caam_process_blob); + +struct caam_blob_priv *caam_blob_gen_init(void) +{ + struct caam_drv_private *ctrlpriv; + struct device *jrdev; + + /* + * caam_blob_gen_init() may expectedly fail with -ENODEV, e.g. when + * CAAM driver didn't probe or when SoC lacks BLOB support. An + * error would be harsh in this case, so we stick to info level. + */ + + jrdev = caam_jr_alloc(); + if (IS_ERR(jrdev)) { + pr_info("job ring requested, but none currently available\n"); + return ERR_PTR(-ENODEV); + } + + ctrlpriv = dev_get_drvdata(jrdev->parent); + if (!ctrlpriv->blob_present) { + dev_info(jrdev, "no hardware blob generation support\n"); + caam_jr_free(jrdev); + return ERR_PTR(-ENODEV); + } + + return container_of(jrdev, struct caam_blob_priv, jrdev); +} +EXPORT_SYMBOL(caam_blob_gen_init); + +void caam_blob_gen_exit(struct caam_blob_priv *priv) +{ + caam_jr_free(&priv->jrdev); +} +EXPORT_SYMBOL(caam_blob_gen_exit); diff --git a/drivers/crypto/caam/ctrl.c b/drivers/crypto/caam/ctrl.c index ca0361b2dbb0..38c4d88a9d03 100644 --- a/drivers/crypto/caam/ctrl.c +++ b/drivers/crypto/caam/ctrl.c @@ -820,12 +820,25 @@ static int caam_probe(struct platform_device *pdev) return -ENOMEM; } - if (ctrlpriv->era < 10) + comp_params = rd_reg32(&ctrl->perfmon.comp_parms_ls); + ctrlpriv->blob_present = !!(comp_params & CTPR_LS_BLOB); + + /* + * Some SoCs like the LS1028A (non-E) indicate CTPR_LS_BLOB support, + * but fail when actually using it due to missing AES support, so + * check both here. + */ + if (ctrlpriv->era < 10) { rng_vid = (rd_reg32(&ctrl->perfmon.cha_id_ls) & CHA_ID_LS_RNG_MASK) >> CHA_ID_LS_RNG_SHIFT; - else + ctrlpriv->blob_present = ctrlpriv->blob_present && + (rd_reg32(&ctrl->perfmon.cha_num_ls) & CHA_ID_LS_AES_MASK); + } else { rng_vid = (rd_reg32(&ctrl->vreg.rng) & CHA_VER_VID_MASK) >> CHA_VER_VID_SHIFT; + ctrlpriv->blob_present = ctrlpriv->blob_present && + (rd_reg32(&ctrl->vreg.aesa) & CHA_VER_MISC_AES_NUM_MASK); + } /* * If SEC has RNG version >= 4 and RNG state handle has not been diff --git a/drivers/crypto/caam/intern.h b/drivers/crypto/caam/intern.h index 7d45b21bd55a..e92210e2ab76 100644 --- a/drivers/crypto/caam/intern.h +++ b/drivers/crypto/caam/intern.h @@ -92,6 +92,7 @@ struct caam_drv_private { */ u8 total_jobrs; /* Total Job Rings in device */ u8 qi_present; /* Nonzero if QI present in device */ + u8 blob_present; /* Nonzero if BLOB support present in device */ u8 mc_en; /* Nonzero if MC f/w is active */ int secvio_irq; /* Security violation interrupt number */ int virt_en; /* Virtualization enabled in CAAM */ diff --git a/drivers/crypto/caam/regs.h b/drivers/crypto/caam/regs.h index 3738625c0250..66d6dad841bb 100644 --- a/drivers/crypto/caam/regs.h +++ b/drivers/crypto/caam/regs.h @@ -320,7 +320,8 @@ struct version_regs { #define CHA_VER_VID_MASK (0xffull << CHA_VER_VID_SHIFT) /* CHA Miscellaneous Information - AESA_MISC specific */ -#define CHA_VER_MISC_AES_GCM BIT(1 + CHA_VER_MISC_SHIFT) +#define CHA_VER_MISC_AES_NUM_MASK GENMASK(7, 0) +#define CHA_VER_MISC_AES_GCM BIT(1 + CHA_VER_MISC_SHIFT) /* CHA Miscellaneous Information - PKHA_MISC specific */ #define CHA_VER_MISC_PKHA_NO_CRYPT BIT(7 + CHA_VER_MISC_SHIFT) @@ -414,6 +415,7 @@ struct caam_perfmon { #define CTPR_MS_PG_SZ_MASK 0x10 #define CTPR_MS_PG_SZ_SHIFT 4 u32 comp_parms_ms; /* CTPR - Compile Parameters Register */ +#define CTPR_LS_BLOB BIT(1) u32 comp_parms_ls; /* CTPR - Compile Parameters Register */ u64 rsvd1[2]; diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index a525a609dfc6..01474daf4548 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -112,16 +112,16 @@ static unsigned long find_available_max_freq(struct devfreq *devfreq) } /** - * get_freq_range() - Get the current freq range + * devfreq_get_freq_range() - Get the current freq range * @devfreq: the devfreq instance * @min_freq: the min frequency * @max_freq: the max frequency * * This takes into consideration all constraints. */ -static void get_freq_range(struct devfreq *devfreq, - unsigned long *min_freq, - unsigned long *max_freq) +void devfreq_get_freq_range(struct devfreq *devfreq, + unsigned long *min_freq, + unsigned long *max_freq) { unsigned long *freq_table = devfreq->profile->freq_table; s32 qos_min_freq, qos_max_freq; @@ -158,6 +158,7 @@ static void get_freq_range(struct devfreq *devfreq, if (*min_freq > *max_freq) *min_freq = *max_freq; } +EXPORT_SYMBOL(devfreq_get_freq_range); /** * devfreq_get_freq_level() - Lookup freq_table for the frequency @@ -418,7 +419,7 @@ int devfreq_update_target(struct devfreq *devfreq, unsigned long freq) err = devfreq->governor->get_target_freq(devfreq, &freq); if (err) return err; - get_freq_range(devfreq, &min_freq, &max_freq); + devfreq_get_freq_range(devfreq, &min_freq, &max_freq); if (freq < min_freq) { freq = min_freq; @@ -785,6 +786,7 @@ struct devfreq *devfreq_add_device(struct device *dev, { struct devfreq *devfreq; struct devfreq_governor *governor; + unsigned long min_freq, max_freq; int err = 0; if (!dev || !profile || !governor_name) { @@ -849,6 +851,8 @@ struct devfreq *devfreq_add_device(struct device *dev, goto err_dev; } + devfreq_get_freq_range(devfreq, &min_freq, &max_freq); + devfreq->suspend_freq = dev_pm_opp_get_suspend_opp_freq(dev); devfreq->opp_table = dev_pm_opp_get_opp_table(dev); if (IS_ERR(devfreq->opp_table)) @@ -1587,7 +1591,7 @@ static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr, unsigned long min_freq, max_freq; mutex_lock(&df->lock); - get_freq_range(df, &min_freq, &max_freq); + devfreq_get_freq_range(df, &min_freq, &max_freq); mutex_unlock(&df->lock); return sprintf(buf, "%lu\n", min_freq); @@ -1641,7 +1645,7 @@ static ssize_t max_freq_show(struct device *dev, struct device_attribute *attr, unsigned long min_freq, max_freq; mutex_lock(&df->lock); - get_freq_range(df, &min_freq, &max_freq); + devfreq_get_freq_range(df, &min_freq, &max_freq); mutex_unlock(&df->lock); return sprintf(buf, "%lu\n", max_freq); @@ -1955,7 +1959,7 @@ static int devfreq_summary_show(struct seq_file *s, void *data) mutex_lock(&devfreq->lock); cur_freq = devfreq->previous_freq; - get_freq_range(devfreq, &min_freq, &max_freq); + devfreq_get_freq_range(devfreq, &min_freq, &max_freq); timer = devfreq->profile->timer; if (IS_SUPPORTED_ATTR(devfreq->governor->attrs, POLLING_INTERVAL)) diff --git a/drivers/devfreq/governor.h b/drivers/devfreq/governor.h index 002a7d67e39d..0adfebc0467a 100644 --- a/drivers/devfreq/governor.h +++ b/drivers/devfreq/governor.h @@ -47,6 +47,31 @@ #define DEVFREQ_GOV_ATTR_POLLING_INTERVAL BIT(0) #define DEVFREQ_GOV_ATTR_TIMER BIT(1) +/** + * struct devfreq_cpu_data - Hold the per-cpu data + * @node: list node + * @dev: reference to cpu device. + * @first_cpu: the cpumask of the first cpu of a policy. + * @opp_table: reference to cpu opp table. + * @cur_freq: the current frequency of the cpu. + * @min_freq: the min frequency of the cpu. + * @max_freq: the max frequency of the cpu. + * + * This structure stores the required cpu_data of a cpu. + * This is auto-populated by the governor. + */ +struct devfreq_cpu_data { + struct list_head node; + + struct device *dev; + unsigned int first_cpu; + + struct opp_table *opp_table; + unsigned int cur_freq; + unsigned int min_freq; + unsigned int max_freq; +}; + /** * struct devfreq_governor - Devfreq policy governor * @node: list node - contains registered devfreq governors @@ -89,6 +114,8 @@ int devm_devfreq_add_governor(struct device *dev, int devfreq_update_status(struct devfreq *devfreq, unsigned long freq); int devfreq_update_target(struct devfreq *devfreq, unsigned long freq); +void devfreq_get_freq_range(struct devfreq *devfreq, unsigned long *min_freq, + unsigned long *max_freq); static inline int devfreq_update_stats(struct devfreq *df) { diff --git a/drivers/devfreq/governor_passive.c b/drivers/devfreq/governor_passive.c index fc09324a03e0..72c67979ebe1 100644 --- a/drivers/devfreq/governor_passive.c +++ b/drivers/devfreq/governor_passive.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only + // SPDX-License-Identifier: GPL-2.0-only /* * linux/drivers/devfreq/governor_passive.c * @@ -8,20 +8,159 @@ */ #include +#include +#include +#include +#include #include #include #include "governor.h" -static int devfreq_passive_get_target_freq(struct devfreq *devfreq, +#define HZ_PER_KHZ 1000 + +static struct devfreq_cpu_data * +get_parent_cpu_data(struct devfreq_passive_data *p_data, + struct cpufreq_policy *policy) +{ + struct devfreq_cpu_data *parent_cpu_data; + + if (!p_data || !policy) + return NULL; + + list_for_each_entry(parent_cpu_data, &p_data->cpu_data_list, node) + if (parent_cpu_data->first_cpu == cpumask_first(policy->related_cpus)) + return parent_cpu_data; + + return NULL; +} + +static unsigned long get_target_freq_by_required_opp(struct device *p_dev, + struct opp_table *p_opp_table, + struct opp_table *opp_table, + unsigned long *freq) +{ + struct dev_pm_opp *opp = NULL, *p_opp = NULL; + unsigned long target_freq; + + if (!p_dev || !p_opp_table || !opp_table || !freq) + return 0; + + p_opp = devfreq_recommended_opp(p_dev, freq, 0); + if (IS_ERR(p_opp)) + return 0; + + opp = dev_pm_opp_xlate_required_opp(p_opp_table, opp_table, p_opp); + dev_pm_opp_put(p_opp); + + if (IS_ERR(opp)) + return 0; + + target_freq = dev_pm_opp_get_freq(opp); + dev_pm_opp_put(opp); + + return target_freq; +} + +static int get_target_freq_with_cpufreq(struct devfreq *devfreq, + unsigned long *target_freq) +{ + struct devfreq_passive_data *p_data = + (struct devfreq_passive_data *)devfreq->data; + struct devfreq_cpu_data *parent_cpu_data; + struct cpufreq_policy *policy; + unsigned long cpu, cpu_cur, cpu_min, cpu_max, cpu_percent; + unsigned long dev_min, dev_max; + unsigned long freq = 0; + int ret = 0; + + for_each_online_cpu(cpu) { + policy = cpufreq_cpu_get(cpu); + if (!policy) { + ret = -EINVAL; + continue; + } + + parent_cpu_data = get_parent_cpu_data(p_data, policy); + if (!parent_cpu_data) { + cpufreq_cpu_put(policy); + continue; + } + + /* Get target freq via required opps */ + cpu_cur = parent_cpu_data->cur_freq * HZ_PER_KHZ; + freq = get_target_freq_by_required_opp(parent_cpu_data->dev, + parent_cpu_data->opp_table, + devfreq->opp_table, &cpu_cur); + if (freq) { + *target_freq = max(freq, *target_freq); + cpufreq_cpu_put(policy); + continue; + } + + /* Use interpolation if required opps is not available */ + devfreq_get_freq_range(devfreq, &dev_min, &dev_max); + + cpu_min = parent_cpu_data->min_freq; + cpu_max = parent_cpu_data->max_freq; + cpu_cur = parent_cpu_data->cur_freq; + + cpu_percent = ((cpu_cur - cpu_min) * 100) / (cpu_max - cpu_min); + freq = dev_min + mult_frac(dev_max - dev_min, cpu_percent, 100); + + *target_freq = max(freq, *target_freq); + cpufreq_cpu_put(policy); + } + + return ret; +} + +static int get_target_freq_with_devfreq(struct devfreq *devfreq, unsigned long *freq) { struct devfreq_passive_data *p_data = (struct devfreq_passive_data *)devfreq->data; struct devfreq *parent_devfreq = (struct devfreq *)p_data->parent; unsigned long child_freq = ULONG_MAX; - struct dev_pm_opp *opp, *p_opp; int i, count; + /* Get target freq via required opps */ + child_freq = get_target_freq_by_required_opp(parent_devfreq->dev.parent, + parent_devfreq->opp_table, + devfreq->opp_table, freq); + if (child_freq) + goto out; + + /* Use interpolation if required opps is not available */ + for (i = 0; i < parent_devfreq->profile->max_state; i++) + if (parent_devfreq->profile->freq_table[i] == *freq) + break; + + if (i == parent_devfreq->profile->max_state) + return -EINVAL; + + if (i < devfreq->profile->max_state) { + child_freq = devfreq->profile->freq_table[i]; + } else { + count = devfreq->profile->max_state; + child_freq = devfreq->profile->freq_table[count - 1]; + } + +out: + *freq = child_freq; + + return 0; +} + +static int devfreq_passive_get_target_freq(struct devfreq *devfreq, + unsigned long *freq) +{ + struct devfreq_passive_data *p_data = + (struct devfreq_passive_data *)devfreq->data; + int ret; + + if (!p_data) + return -EINVAL; + /* * If the devfreq device with passive governor has the specific method * to determine the next frequency, should use the get_target_freq() @@ -30,75 +169,178 @@ static int devfreq_passive_get_target_freq(struct devfreq *devfreq, if (p_data->get_target_freq) return p_data->get_target_freq(devfreq, freq); - /* - * If the parent and passive devfreq device uses the OPP table, - * get the next frequency by using the OPP table. - */ + switch (p_data->parent_type) { + case DEVFREQ_PARENT_DEV: + ret = get_target_freq_with_devfreq(devfreq, freq); + break; + case CPUFREQ_PARENT_DEV: + ret = get_target_freq_with_cpufreq(devfreq, freq); + break; + default: + ret = -EINVAL; + dev_err(&devfreq->dev, "Invalid parent type\n"); + break; + } - /* - * - parent devfreq device uses the governors except for passive. - * - passive devfreq device uses the passive governor. - * - * Each devfreq has the OPP table. After deciding the new frequency - * from the governor of parent devfreq device, the passive governor - * need to get the index of new frequency on OPP table of parent - * device. And then the index is used for getting the suitable - * new frequency for passive devfreq device. - */ - if (!devfreq->profile || !devfreq->profile->freq_table - || devfreq->profile->max_state <= 0) - return -EINVAL; + return ret; +} - /* - * The passive governor have to get the correct frequency from OPP - * list of parent device. Because in this case, *freq is temporary - * value which is decided by ondemand governor. - */ - if (devfreq->opp_table && parent_devfreq->opp_table) { - p_opp = devfreq_recommended_opp(parent_devfreq->dev.parent, - freq, 0); - if (IS_ERR(p_opp)) - return PTR_ERR(p_opp); - - opp = dev_pm_opp_xlate_required_opp(parent_devfreq->opp_table, - devfreq->opp_table, p_opp); - dev_pm_opp_put(p_opp); - - if (IS_ERR(opp)) - goto no_required_opp; - - *freq = dev_pm_opp_get_freq(opp); - dev_pm_opp_put(opp); +static int cpufreq_passive_notifier_call(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct devfreq_passive_data *p_data = + container_of(nb, struct devfreq_passive_data, nb); + struct devfreq *devfreq = (struct devfreq *)p_data->this; + struct devfreq_cpu_data *parent_cpu_data; + struct cpufreq_freqs *freqs = ptr; + unsigned int cur_freq; + int ret; + if (event != CPUFREQ_POSTCHANGE || !freqs) return 0; + + parent_cpu_data = get_parent_cpu_data(p_data, freqs->policy); + if (!parent_cpu_data || parent_cpu_data->cur_freq == freqs->new) + return 0; + + cur_freq = parent_cpu_data->cur_freq; + parent_cpu_data->cur_freq = freqs->new; + + mutex_lock(&devfreq->lock); + ret = devfreq_update_target(devfreq, freqs->new); + mutex_unlock(&devfreq->lock); + if (ret) { + parent_cpu_data->cur_freq = cur_freq; + dev_err(&devfreq->dev, "failed to update the frequency.\n"); + return ret; } -no_required_opp: - /* - * Get the OPP table's index of decided frequency by governor - * of parent device. - */ - for (i = 0; i < parent_devfreq->profile->max_state; i++) - if (parent_devfreq->profile->freq_table[i] == *freq) - break; - - if (i == parent_devfreq->profile->max_state) - return -EINVAL; - - /* Get the suitable frequency by using index of parent device. */ - if (i < devfreq->profile->max_state) { - child_freq = devfreq->profile->freq_table[i]; - } else { - count = devfreq->profile->max_state; - child_freq = devfreq->profile->freq_table[count - 1]; - } - - /* Return the suitable frequency for passive device. */ - *freq = child_freq; - return 0; } +static int cpufreq_passive_unregister_notifier(struct devfreq *devfreq) +{ + struct devfreq_passive_data *p_data + = (struct devfreq_passive_data *)devfreq->data; + struct devfreq_cpu_data *parent_cpu_data; + int cpu, ret = 0; + + if (p_data->nb.notifier_call) { + ret = cpufreq_unregister_notifier(&p_data->nb, + CPUFREQ_TRANSITION_NOTIFIER); + if (ret < 0) + return ret; + } + + for_each_possible_cpu(cpu) { + struct cpufreq_policy *policy = cpufreq_cpu_get(cpu); + if (!policy) { + ret = -EINVAL; + continue; + } + + parent_cpu_data = get_parent_cpu_data(p_data, policy); + if (!parent_cpu_data) { + cpufreq_cpu_put(policy); + continue; + } + + list_del(&parent_cpu_data->node); + if (parent_cpu_data->opp_table) + dev_pm_opp_put_opp_table(parent_cpu_data->opp_table); + kfree(parent_cpu_data); + cpufreq_cpu_put(policy); + } + + return ret; +} + +static int cpufreq_passive_register_notifier(struct devfreq *devfreq) +{ + struct devfreq_passive_data *p_data + = (struct devfreq_passive_data *)devfreq->data; + struct device *dev = devfreq->dev.parent; + struct opp_table *opp_table = NULL; + struct devfreq_cpu_data *parent_cpu_data; + struct cpufreq_policy *policy; + struct device *cpu_dev; + unsigned int cpu; + int ret; + + p_data->cpu_data_list + = (struct list_head)LIST_HEAD_INIT(p_data->cpu_data_list); + + p_data->nb.notifier_call = cpufreq_passive_notifier_call; + ret = cpufreq_register_notifier(&p_data->nb, CPUFREQ_TRANSITION_NOTIFIER); + if (ret) { + dev_err(dev, "failed to register cpufreq notifier\n"); + p_data->nb.notifier_call = NULL; + goto err; + } + + for_each_possible_cpu(cpu) { + policy = cpufreq_cpu_get(cpu); + if (!policy) { + ret = -EPROBE_DEFER; + goto err; + } + + parent_cpu_data = get_parent_cpu_data(p_data, policy); + if (parent_cpu_data) { + cpufreq_cpu_put(policy); + continue; + } + + parent_cpu_data = kzalloc(sizeof(*parent_cpu_data), + GFP_KERNEL); + if (!parent_cpu_data) { + ret = -ENOMEM; + goto err_put_policy; + } + + cpu_dev = get_cpu_device(cpu); + if (!cpu_dev) { + dev_err(dev, "failed to get cpu device\n"); + ret = -ENODEV; + goto err_free_cpu_data; + } + + opp_table = dev_pm_opp_get_opp_table(cpu_dev); + if (IS_ERR(opp_table)) { + dev_err(dev, "failed to get opp_table of cpu%d\n", cpu); + ret = PTR_ERR(opp_table); + goto err_free_cpu_data; + } + + parent_cpu_data->dev = cpu_dev; + parent_cpu_data->opp_table = opp_table; + parent_cpu_data->first_cpu = cpumask_first(policy->related_cpus); + parent_cpu_data->cur_freq = policy->cur; + parent_cpu_data->min_freq = policy->cpuinfo.min_freq; + parent_cpu_data->max_freq = policy->cpuinfo.max_freq; + + list_add_tail(&parent_cpu_data->node, &p_data->cpu_data_list); + cpufreq_cpu_put(policy); + } + + mutex_lock(&devfreq->lock); + ret = devfreq_update_target(devfreq, 0L); + mutex_unlock(&devfreq->lock); + if (ret) + dev_err(dev, "failed to update the frequency\n"); + + return ret; + +err_free_cpu_data: + kfree(parent_cpu_data); +err_put_policy: + cpufreq_cpu_put(policy); +err: + WARN_ON(cpufreq_passive_unregister_notifier(devfreq)); + + return ret; +} + static int devfreq_passive_notifier_call(struct notifier_block *nb, unsigned long event, void *ptr) { @@ -131,30 +373,55 @@ static int devfreq_passive_notifier_call(struct notifier_block *nb, return NOTIFY_DONE; } -static int devfreq_passive_event_handler(struct devfreq *devfreq, - unsigned int event, void *data) +static int devfreq_passive_unregister_notifier(struct devfreq *devfreq) +{ + struct devfreq_passive_data *p_data + = (struct devfreq_passive_data *)devfreq->data; + struct devfreq *parent = (struct devfreq *)p_data->parent; + struct notifier_block *nb = &p_data->nb; + + return devfreq_unregister_notifier(parent, nb, DEVFREQ_TRANSITION_NOTIFIER); +} + +static int devfreq_passive_register_notifier(struct devfreq *devfreq) { struct devfreq_passive_data *p_data = (struct devfreq_passive_data *)devfreq->data; struct devfreq *parent = (struct devfreq *)p_data->parent; struct notifier_block *nb = &p_data->nb; - int ret = 0; if (!parent) return -EPROBE_DEFER; + nb->notifier_call = devfreq_passive_notifier_call; + return devfreq_register_notifier(parent, nb, DEVFREQ_TRANSITION_NOTIFIER); +} + +static int devfreq_passive_event_handler(struct devfreq *devfreq, + unsigned int event, void *data) +{ + struct devfreq_passive_data *p_data + = (struct devfreq_passive_data *)devfreq->data; + int ret = 0; + + if (!p_data) + return -EINVAL; + + if (!p_data->this) + p_data->this = devfreq; + switch (event) { case DEVFREQ_GOV_START: - if (!p_data->this) - p_data->this = devfreq; - - nb->notifier_call = devfreq_passive_notifier_call; - ret = devfreq_register_notifier(parent, nb, - DEVFREQ_TRANSITION_NOTIFIER); + if (p_data->parent_type == DEVFREQ_PARENT_DEV) + ret = devfreq_passive_register_notifier(devfreq); + else if (p_data->parent_type == CPUFREQ_PARENT_DEV) + ret = cpufreq_passive_register_notifier(devfreq); break; case DEVFREQ_GOV_STOP: - WARN_ON(devfreq_unregister_notifier(parent, nb, - DEVFREQ_TRANSITION_NOTIFIER)); + if (p_data->parent_type == DEVFREQ_PARENT_DEV) + WARN_ON(devfreq_passive_unregister_notifier(devfreq)); + else if (p_data->parent_type == CPUFREQ_PARENT_DEV) + WARN_ON(cpufreq_passive_unregister_notifier(devfreq)); break; default: break; diff --git a/drivers/devfreq/rk3399_dmc.c b/drivers/devfreq/rk3399_dmc.c index 293857ebfd75..daff40702615 100644 --- a/drivers/devfreq/rk3399_dmc.c +++ b/drivers/devfreq/rk3399_dmc.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -20,55 +21,49 @@ #include #include +#include #include #include -struct dram_timing { - unsigned int ddr3_speed_bin; - unsigned int pd_idle; - unsigned int sr_idle; - unsigned int sr_mc_gate_idle; - unsigned int srpd_lite_idle; - unsigned int standby_idle; - unsigned int auto_pd_dis_freq; - unsigned int dram_dll_dis_freq; - unsigned int phy_dll_dis_freq; - unsigned int ddr3_odt_dis_freq; - unsigned int ddr3_drv; - unsigned int ddr3_odt; - unsigned int phy_ddr3_ca_drv; - unsigned int phy_ddr3_dq_drv; - unsigned int phy_ddr3_odt; - unsigned int lpddr3_odt_dis_freq; - unsigned int lpddr3_drv; - unsigned int lpddr3_odt; - unsigned int phy_lpddr3_ca_drv; - unsigned int phy_lpddr3_dq_drv; - unsigned int phy_lpddr3_odt; - unsigned int lpddr4_odt_dis_freq; - unsigned int lpddr4_drv; - unsigned int lpddr4_dq_odt; - unsigned int lpddr4_ca_odt; - unsigned int phy_lpddr4_ca_drv; - unsigned int phy_lpddr4_ck_cs_drv; - unsigned int phy_lpddr4_dq_drv; - unsigned int phy_lpddr4_odt; -}; +#define NS_TO_CYCLE(NS, MHz) (((NS) * (MHz)) / NSEC_PER_USEC) + +#define RK3399_SET_ODT_PD_0_SR_IDLE GENMASK(7, 0) +#define RK3399_SET_ODT_PD_0_SR_MC_GATE_IDLE GENMASK(15, 8) +#define RK3399_SET_ODT_PD_0_STANDBY_IDLE GENMASK(31, 16) + +#define RK3399_SET_ODT_PD_1_PD_IDLE GENMASK(11, 0) +#define RK3399_SET_ODT_PD_1_SRPD_LITE_IDLE GENMASK(27, 16) + +#define RK3399_SET_ODT_PD_2_ODT_ENABLE BIT(0) struct rk3399_dmcfreq { struct device *dev; struct devfreq *devfreq; + struct devfreq_dev_profile profile; struct devfreq_simple_ondemand_data ondemand_data; struct clk *dmc_clk; struct devfreq_event_dev *edev; struct mutex lock; - struct dram_timing timing; struct regulator *vdd_center; struct regmap *regmap_pmu; unsigned long rate, target_rate; unsigned long volt, target_volt; unsigned int odt_dis_freq; - int odt_pd_arg0, odt_pd_arg1; + + unsigned int pd_idle_ns; + unsigned int sr_idle_ns; + unsigned int sr_mc_gate_idle_ns; + unsigned int srpd_lite_idle_ns; + unsigned int standby_idle_ns; + unsigned int ddr3_odt_dis_freq; + unsigned int lpddr3_odt_dis_freq; + unsigned int lpddr4_odt_dis_freq; + + unsigned int pd_idle_dis_freq; + unsigned int sr_idle_dis_freq; + unsigned int sr_mc_gate_idle_dis_freq; + unsigned int srpd_lite_idle_dis_freq; + unsigned int standby_idle_dis_freq; }; static int rk3399_dmcfreq_target(struct device *dev, unsigned long *freq, @@ -78,10 +73,14 @@ static int rk3399_dmcfreq_target(struct device *dev, unsigned long *freq, struct dev_pm_opp *opp; unsigned long old_clk_rate = dmcfreq->rate; unsigned long target_volt, target_rate; + unsigned int ddrcon_mhz; struct arm_smccc_res res; - bool odt_enable = false; int err; + u32 odt_pd_arg0 = 0; + u32 odt_pd_arg1 = 0; + u32 odt_pd_arg2 = 0; + opp = devfreq_recommended_opp(dev, freq, flags); if (IS_ERR(opp)) return PTR_ERR(opp); @@ -95,19 +94,71 @@ static int rk3399_dmcfreq_target(struct device *dev, unsigned long *freq, mutex_lock(&dmcfreq->lock); + /* + * Ensure power-domain transitions don't interfere with ARM Trusted + * Firmware power-domain idling. + */ + err = rockchip_pmu_block(); + if (err) { + dev_err(dev, "Failed to block PMU: %d\n", err); + goto out_unlock; + } + + /* + * Some idle parameters may be based on the DDR controller clock, which + * is half of the DDR frequency. + * pd_idle and standby_idle are based on the controller clock cycle. + * sr_idle_cycle, sr_mc_gate_idle_cycle, and srpd_lite_idle_cycle + * are based on the 1024 controller clock cycle + */ + ddrcon_mhz = target_rate / USEC_PER_SEC / 2; + + u32p_replace_bits(&odt_pd_arg1, + NS_TO_CYCLE(dmcfreq->pd_idle_ns, ddrcon_mhz), + RK3399_SET_ODT_PD_1_PD_IDLE); + u32p_replace_bits(&odt_pd_arg0, + NS_TO_CYCLE(dmcfreq->standby_idle_ns, ddrcon_mhz), + RK3399_SET_ODT_PD_0_STANDBY_IDLE); + u32p_replace_bits(&odt_pd_arg0, + DIV_ROUND_UP(NS_TO_CYCLE(dmcfreq->sr_idle_ns, + ddrcon_mhz), 1024), + RK3399_SET_ODT_PD_0_SR_IDLE); + u32p_replace_bits(&odt_pd_arg0, + DIV_ROUND_UP(NS_TO_CYCLE(dmcfreq->sr_mc_gate_idle_ns, + ddrcon_mhz), 1024), + RK3399_SET_ODT_PD_0_SR_MC_GATE_IDLE); + u32p_replace_bits(&odt_pd_arg1, + DIV_ROUND_UP(NS_TO_CYCLE(dmcfreq->srpd_lite_idle_ns, + ddrcon_mhz), 1024), + RK3399_SET_ODT_PD_1_SRPD_LITE_IDLE); + if (dmcfreq->regmap_pmu) { + if (target_rate >= dmcfreq->sr_idle_dis_freq) + odt_pd_arg0 &= ~RK3399_SET_ODT_PD_0_SR_IDLE; + + if (target_rate >= dmcfreq->sr_mc_gate_idle_dis_freq) + odt_pd_arg0 &= ~RK3399_SET_ODT_PD_0_SR_MC_GATE_IDLE; + + if (target_rate >= dmcfreq->standby_idle_dis_freq) + odt_pd_arg0 &= ~RK3399_SET_ODT_PD_0_STANDBY_IDLE; + + if (target_rate >= dmcfreq->pd_idle_dis_freq) + odt_pd_arg1 &= ~RK3399_SET_ODT_PD_1_PD_IDLE; + + if (target_rate >= dmcfreq->srpd_lite_idle_dis_freq) + odt_pd_arg1 &= ~RK3399_SET_ODT_PD_1_SRPD_LITE_IDLE; + if (target_rate >= dmcfreq->odt_dis_freq) - odt_enable = true; + odt_pd_arg2 |= RK3399_SET_ODT_PD_2_ODT_ENABLE; /* * This makes a SMC call to the TF-A to set the DDR PD * (power-down) timings and to enable or disable the * ODT (on-die termination) resistors. */ - arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, dmcfreq->odt_pd_arg0, - dmcfreq->odt_pd_arg1, - ROCKCHIP_SIP_CONFIG_DRAM_SET_ODT_PD, - odt_enable, 0, 0, 0, &res); + arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, odt_pd_arg0, odt_pd_arg1, + ROCKCHIP_SIP_CONFIG_DRAM_SET_ODT_PD, odt_pd_arg2, + 0, 0, 0, &res); } /* @@ -158,6 +209,8 @@ static int rk3399_dmcfreq_target(struct device *dev, unsigned long *freq, dmcfreq->volt = target_volt; out: + rockchip_pmu_unblock(); +out_unlock: mutex_unlock(&dmcfreq->lock); return err; } @@ -189,13 +242,6 @@ static int rk3399_dmcfreq_get_cur_freq(struct device *dev, unsigned long *freq) return 0; } -static struct devfreq_dev_profile rk3399_devfreq_dmc_profile = { - .polling_ms = 200, - .target = rk3399_dmcfreq_target, - .get_dev_status = rk3399_dmcfreq_get_dev_status, - .get_cur_freq = rk3399_dmcfreq_get_cur_freq, -}; - static __maybe_unused int rk3399_dmcfreq_suspend(struct device *dev) { struct rk3399_dmcfreq *dmcfreq = dev_get_drvdata(dev); @@ -238,69 +284,48 @@ static __maybe_unused int rk3399_dmcfreq_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(rk3399_dmcfreq_pm, rk3399_dmcfreq_suspend, rk3399_dmcfreq_resume); -static int of_get_ddr_timings(struct dram_timing *timing, - struct device_node *np) +static int rk3399_dmcfreq_of_props(struct rk3399_dmcfreq *data, + struct device_node *np) { int ret = 0; - ret = of_property_read_u32(np, "rockchip,ddr3_speed_bin", - &timing->ddr3_speed_bin); - ret |= of_property_read_u32(np, "rockchip,pd_idle", - &timing->pd_idle); - ret |= of_property_read_u32(np, "rockchip,sr_idle", - &timing->sr_idle); - ret |= of_property_read_u32(np, "rockchip,sr_mc_gate_idle", - &timing->sr_mc_gate_idle); - ret |= of_property_read_u32(np, "rockchip,srpd_lite_idle", - &timing->srpd_lite_idle); - ret |= of_property_read_u32(np, "rockchip,standby_idle", - &timing->standby_idle); - ret |= of_property_read_u32(np, "rockchip,auto_pd_dis_freq", - &timing->auto_pd_dis_freq); - ret |= of_property_read_u32(np, "rockchip,dram_dll_dis_freq", - &timing->dram_dll_dis_freq); - ret |= of_property_read_u32(np, "rockchip,phy_dll_dis_freq", - &timing->phy_dll_dis_freq); + /* + * These are all optional, and serve as minimum bounds. Give them large + * (i.e., never "disabled") values if the DT doesn't specify one. + */ + data->pd_idle_dis_freq = + data->sr_idle_dis_freq = + data->sr_mc_gate_idle_dis_freq = + data->srpd_lite_idle_dis_freq = + data->standby_idle_dis_freq = UINT_MAX; + + ret |= of_property_read_u32(np, "rockchip,pd-idle-ns", + &data->pd_idle_ns); + ret |= of_property_read_u32(np, "rockchip,sr-idle-ns", + &data->sr_idle_ns); + ret |= of_property_read_u32(np, "rockchip,sr-mc-gate-idle-ns", + &data->sr_mc_gate_idle_ns); + ret |= of_property_read_u32(np, "rockchip,srpd-lite-idle-ns", + &data->srpd_lite_idle_ns); + ret |= of_property_read_u32(np, "rockchip,standby-idle-ns", + &data->standby_idle_ns); ret |= of_property_read_u32(np, "rockchip,ddr3_odt_dis_freq", - &timing->ddr3_odt_dis_freq); - ret |= of_property_read_u32(np, "rockchip,ddr3_drv", - &timing->ddr3_drv); - ret |= of_property_read_u32(np, "rockchip,ddr3_odt", - &timing->ddr3_odt); - ret |= of_property_read_u32(np, "rockchip,phy_ddr3_ca_drv", - &timing->phy_ddr3_ca_drv); - ret |= of_property_read_u32(np, "rockchip,phy_ddr3_dq_drv", - &timing->phy_ddr3_dq_drv); - ret |= of_property_read_u32(np, "rockchip,phy_ddr3_odt", - &timing->phy_ddr3_odt); + &data->ddr3_odt_dis_freq); ret |= of_property_read_u32(np, "rockchip,lpddr3_odt_dis_freq", - &timing->lpddr3_odt_dis_freq); - ret |= of_property_read_u32(np, "rockchip,lpddr3_drv", - &timing->lpddr3_drv); - ret |= of_property_read_u32(np, "rockchip,lpddr3_odt", - &timing->lpddr3_odt); - ret |= of_property_read_u32(np, "rockchip,phy_lpddr3_ca_drv", - &timing->phy_lpddr3_ca_drv); - ret |= of_property_read_u32(np, "rockchip,phy_lpddr3_dq_drv", - &timing->phy_lpddr3_dq_drv); - ret |= of_property_read_u32(np, "rockchip,phy_lpddr3_odt", - &timing->phy_lpddr3_odt); + &data->lpddr3_odt_dis_freq); ret |= of_property_read_u32(np, "rockchip,lpddr4_odt_dis_freq", - &timing->lpddr4_odt_dis_freq); - ret |= of_property_read_u32(np, "rockchip,lpddr4_drv", - &timing->lpddr4_drv); - ret |= of_property_read_u32(np, "rockchip,lpddr4_dq_odt", - &timing->lpddr4_dq_odt); - ret |= of_property_read_u32(np, "rockchip,lpddr4_ca_odt", - &timing->lpddr4_ca_odt); - ret |= of_property_read_u32(np, "rockchip,phy_lpddr4_ca_drv", - &timing->phy_lpddr4_ca_drv); - ret |= of_property_read_u32(np, "rockchip,phy_lpddr4_ck_cs_drv", - &timing->phy_lpddr4_ck_cs_drv); - ret |= of_property_read_u32(np, "rockchip,phy_lpddr4_dq_drv", - &timing->phy_lpddr4_dq_drv); - ret |= of_property_read_u32(np, "rockchip,phy_lpddr4_odt", - &timing->phy_lpddr4_odt); + &data->lpddr4_odt_dis_freq); + + ret |= of_property_read_u32(np, "rockchip,pd-idle-dis-freq-hz", + &data->pd_idle_dis_freq); + ret |= of_property_read_u32(np, "rockchip,sr-idle-dis-freq-hz", + &data->sr_idle_dis_freq); + ret |= of_property_read_u32(np, "rockchip,sr-mc-gate-idle-dis-freq-hz", + &data->sr_mc_gate_idle_dis_freq); + ret |= of_property_read_u32(np, "rockchip,srpd-lite-idle-dis-freq-hz", + &data->srpd_lite_idle_dis_freq); + ret |= of_property_read_u32(np, "rockchip,standby-idle-dis-freq-hz", + &data->standby_idle_dis_freq); return ret; } @@ -311,8 +336,7 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct device_node *np = pdev->dev.of_node, *node; struct rk3399_dmcfreq *data; - int ret, index, size; - uint32_t *timing; + int ret; struct dev_pm_opp *opp; u32 ddr_type; u32 val; @@ -343,26 +367,7 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev) return ret; } - /* - * Get dram timing and pass it to arm trust firmware, - * the dram driver in arm trust firmware will get these - * timing and to do dram initial. - */ - if (!of_get_ddr_timings(&data->timing, np)) { - timing = &data->timing.ddr3_speed_bin; - size = sizeof(struct dram_timing) / 4; - for (index = 0; index < size; index++) { - arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, *timing++, index, - ROCKCHIP_SIP_CONFIG_DRAM_SET_PARAM, - 0, 0, 0, 0, &res); - if (res.a0) { - dev_err(dev, "Failed to set dram param: %ld\n", - res.a0); - ret = -EINVAL; - goto err_edev; - } - } - } + rk3399_dmcfreq_of_props(data, np); node = of_parse_phandle(np, "rockchip,pmu", 0); if (!node) @@ -381,13 +386,13 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev) switch (ddr_type) { case RK3399_PMUGRF_DDRTYPE_DDR3: - data->odt_dis_freq = data->timing.ddr3_odt_dis_freq; + data->odt_dis_freq = data->ddr3_odt_dis_freq; break; case RK3399_PMUGRF_DDRTYPE_LPDDR3: - data->odt_dis_freq = data->timing.lpddr3_odt_dis_freq; + data->odt_dis_freq = data->lpddr3_odt_dis_freq; break; case RK3399_PMUGRF_DDRTYPE_LPDDR4: - data->odt_dis_freq = data->timing.lpddr4_odt_dis_freq; + data->odt_dis_freq = data->lpddr4_odt_dis_freq; break; default: ret = -EINVAL; @@ -399,63 +404,46 @@ no_pmu: ROCKCHIP_SIP_CONFIG_DRAM_INIT, 0, 0, 0, 0, &res); - /* - * In TF-A there is a platform SIP call to set the PD (power-down) - * timings and to enable or disable the ODT (on-die termination). - * This call needs three arguments as follows: - * - * arg0: - * bit[0-7] : sr_idle - * bit[8-15] : sr_mc_gate_idle - * bit[16-31] : standby idle - * arg1: - * bit[0-11] : pd_idle - * bit[16-27] : srpd_lite_idle - * arg2: - * bit[0] : odt enable - */ - data->odt_pd_arg0 = (data->timing.sr_idle & 0xff) | - ((data->timing.sr_mc_gate_idle & 0xff) << 8) | - ((data->timing.standby_idle & 0xffff) << 16); - data->odt_pd_arg1 = (data->timing.pd_idle & 0xfff) | - ((data->timing.srpd_lite_idle & 0xfff) << 16); - /* * We add a devfreq driver to our parent since it has a device tree node * with operating points. */ - if (dev_pm_opp_of_add_table(dev)) { + if (devm_pm_opp_of_add_table(dev)) { dev_err(dev, "Invalid operating-points in device tree.\n"); ret = -EINVAL; goto err_edev; } - of_property_read_u32(np, "upthreshold", - &data->ondemand_data.upthreshold); - of_property_read_u32(np, "downdifferential", - &data->ondemand_data.downdifferential); + data->ondemand_data.upthreshold = 25; + data->ondemand_data.downdifferential = 15; data->rate = clk_get_rate(data->dmc_clk); opp = devfreq_recommended_opp(dev, &data->rate, 0); if (IS_ERR(opp)) { ret = PTR_ERR(opp); - goto err_free_opp; + goto err_edev; } data->rate = dev_pm_opp_get_freq(opp); data->volt = dev_pm_opp_get_voltage(opp); dev_pm_opp_put(opp); - rk3399_devfreq_dmc_profile.initial_freq = data->rate; + data->profile = (struct devfreq_dev_profile) { + .polling_ms = 200, + .target = rk3399_dmcfreq_target, + .get_dev_status = rk3399_dmcfreq_get_dev_status, + .get_cur_freq = rk3399_dmcfreq_get_cur_freq, + .initial_freq = data->rate, + }; data->devfreq = devm_devfreq_add_device(dev, - &rk3399_devfreq_dmc_profile, + &data->profile, DEVFREQ_GOV_SIMPLE_ONDEMAND, &data->ondemand_data); if (IS_ERR(data->devfreq)) { ret = PTR_ERR(data->devfreq); - goto err_free_opp; + goto err_edev; } devm_devfreq_register_opp_notifier(dev, data->devfreq); @@ -465,8 +453,6 @@ no_pmu: return 0; -err_free_opp: - dev_pm_opp_of_remove_table(&pdev->dev); err_edev: devfreq_event_disable_edev(data->edev); @@ -477,11 +463,7 @@ static int rk3399_dmcfreq_remove(struct platform_device *pdev) { struct rk3399_dmcfreq *dmcfreq = dev_get_drvdata(&pdev->dev); - /* - * Before remove the opp table we need to unregister the opp notifier. - */ - devm_devfreq_unregister_opp_notifier(dmcfreq->dev, dmcfreq->devfreq); - dev_pm_opp_of_remove_table(dmcfreq->dev); + devfreq_event_disable_edev(dmcfreq->edev); return 0; } diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index a95a7cbc4a59..70da5931082f 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -697,6 +697,14 @@ config HID_MAYFLASH Say Y here if you have HJZ Mayflash PS3 game controller adapters and want to enable force feedback support. +config HID_MEGAWORLD_FF + tristate "Mega World based game controller force feedback support" + depends on USB_HID + select INPUT_FF_MEMLESS + help + Say Y here if you have a Mega World based game controller and want + to have force feedback support for it. + config HID_REDRAGON tristate "Redragon keyboards" depends on HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 345ac5581bd8..cac2cbe26d11 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -77,6 +77,7 @@ obj-$(CONFIG_HID_MAGICMOUSE) += hid-magicmouse.o obj-$(CONFIG_HID_MALTRON) += hid-maltron.o obj-$(CONFIG_HID_MCP2221) += hid-mcp2221.o obj-$(CONFIG_HID_MAYFLASH) += hid-mf.o +obj-$(CONFIG_HID_MEGAWORLD_FF) += hid-megaworld.o obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o obj-$(CONFIG_HID_MULTITOUCH) += hid-multitouch.o diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_client.c b/drivers/hid/amd-sfh-hid/amd_sfh_client.c index c5de0ec4f9d0..0f770a2b47ff 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_client.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_client.c @@ -141,6 +141,24 @@ u32 amd_sfh_wait_for_response(struct amd_mp2_dev *mp2, u8 sid, u32 sensor_sts) return sensor_sts; } +const char *get_sensor_name(int idx) +{ + switch (idx) { + case accel_idx: + return "accelerometer"; + case gyro_idx: + return "gyroscope"; + case mag_idx: + return "magnetometer"; + case als_idx: + return "ALS"; + case HPD_IDX: + return "HPD"; + default: + return "unknown sensor type"; + } +} + int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata) { struct amd_input_data *in_data = &privdata->in_data; @@ -219,13 +237,27 @@ int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata) (privdata, cl_data->sensor_idx[i], SENSOR_DISABLED); if (status != SENSOR_ENABLED) cl_data->sensor_sts[i] = SENSOR_DISABLED; - dev_dbg(dev, "sid 0x%x status 0x%x\n", - cl_data->sensor_idx[i], cl_data->sensor_sts[i]); + dev_dbg(dev, "sid 0x%x (%s) status 0x%x\n", + cl_data->sensor_idx[i], + get_sensor_name(cl_data->sensor_idx[i]), + cl_data->sensor_sts[i]); goto cleanup; } } - dev_dbg(dev, "sid 0x%x status 0x%x\n", - cl_data->sensor_idx[i], cl_data->sensor_sts[i]); + dev_dbg(dev, "sid 0x%x (%s) status 0x%x\n", + cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]), + cl_data->sensor_sts[i]); + } + if (privdata->mp2_ops->discovery_status && + privdata->mp2_ops->discovery_status(privdata) == 0) { + amd_sfh_hid_client_deinit(privdata); + for (i = 0; i < cl_data->num_hid_devices; i++) { + devm_kfree(dev, cl_data->feature_report[i]); + devm_kfree(dev, in_data->input_report[i]); + devm_kfree(dev, cl_data->report_descr[i]); + } + dev_warn(dev, "Failed to discover, sensors not enabled\n"); + return -EOPNOTSUPP; } schedule_delayed_work(&cl_data->work_buffer, msecs_to_jiffies(AMD_SFH_IDLE_LOOP)); return 0; @@ -257,8 +289,9 @@ int amd_sfh_hid_client_deinit(struct amd_mp2_dev *privdata) (privdata, cl_data->sensor_idx[i], SENSOR_DISABLED); if (status != SENSOR_ENABLED) cl_data->sensor_sts[i] = SENSOR_DISABLED; - dev_dbg(&privdata->pdev->dev, "stopping sid 0x%x status 0x%x\n", - cl_data->sensor_idx[i], cl_data->sensor_sts[i]); + dev_dbg(&privdata->pdev->dev, "stopping sid 0x%x (%s) status 0x%x\n", + cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]), + cl_data->sensor_sts[i]); } } diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_hid.c b/drivers/hid/amd-sfh-hid/amd_sfh_hid.c index 2bf97b6ac973..1089134030b0 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_hid.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_hid.c @@ -12,6 +12,7 @@ #include #include "amd_sfh_hid.h" +#include "amd_sfh_pcie.h" #define AMD_SFH_RESPONSE_TIMEOUT 1500 @@ -120,6 +121,8 @@ static struct hid_ll_driver amdtp_hid_ll_driver = { int amdtp_hid_probe(u32 cur_hid_dev, struct amdtp_cl_data *cli_data) { + struct amd_mp2_dev *mp2 = container_of(cli_data->in_data, struct amd_mp2_dev, in_data); + struct device *dev = &mp2->pdev->dev; struct hid_device *hid; struct amdtp_hid_data *hid_data; int rc; @@ -141,10 +144,12 @@ int amdtp_hid_probe(u32 cur_hid_dev, struct amdtp_cl_data *cli_data) hid->driver_data = hid_data; cli_data->hid_sensor_hubs[cur_hid_dev] = hid; - hid->bus = BUS_AMD_AMDTP; + strscpy(hid->phys, dev->driver ? dev->driver->name : dev_name(dev), + sizeof(hid->phys)); + hid->bus = BUS_AMD_SFH; hid->vendor = AMD_SFH_HID_VENDOR; hid->product = AMD_SFH_HID_PRODUCT; - snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "hid-amdtp", + snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "hid-amdsfh", hid->vendor, hid->product); rc = hid_add_device(hid); diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_hid.h b/drivers/hid/amd-sfh-hid/amd_sfh_hid.h index c60abd38054c..ad264db63180 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_hid.h +++ b/drivers/hid/amd-sfh-hid/amd_sfh_hid.h @@ -12,7 +12,6 @@ #define AMDSFH_HID_H #define MAX_HID_DEVICES 5 -#define BUS_AMD_AMDTP 0x20 #define AMD_SFH_HID_VENDOR 0x1022 #define AMD_SFH_HID_PRODUCT 0x0001 diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c index 6b5fd90b0bd1..dadc491bbf6b 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c @@ -130,6 +130,12 @@ static int amd_sfh_irq_init_v2(struct amd_mp2_dev *privdata) return 0; } +static int amd_sfh_dis_sts_v2(struct amd_mp2_dev *privdata) +{ + return (readl(privdata->mmio + AMD_P2C_MSG(1)) & + SENSOR_DISCOVERY_STATUS_MASK) >> SENSOR_DISCOVERY_STATUS_SHIFT; +} + void amd_start_sensor(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info info) { union sfh_cmd_param cmd_param; @@ -245,6 +251,7 @@ static const struct amd_mp2_ops amd_sfh_ops_v2 = { .response = amd_sfh_wait_response_v2, .clear_intr = amd_sfh_clear_intr_v2, .init_intr = amd_sfh_irq_init_v2, + .discovery_status = amd_sfh_dis_sts_v2, }; static const struct amd_mp2_ops amd_sfh_ops = { @@ -346,8 +353,9 @@ static int __maybe_unused amd_mp2_pci_resume(struct device *dev) (mp2, cl_data->sensor_idx[i], SENSOR_ENABLED); if (status == SENSOR_ENABLED) cl_data->sensor_sts[i] = SENSOR_ENABLED; - dev_dbg(dev, "resume sid 0x%x status 0x%x\n", - cl_data->sensor_idx[i], cl_data->sensor_sts[i]); + dev_dbg(dev, "suspend sid 0x%x (%s) status 0x%x\n", + cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]), + cl_data->sensor_sts[i]); } } @@ -371,8 +379,9 @@ static int __maybe_unused amd_mp2_pci_suspend(struct device *dev) (mp2, cl_data->sensor_idx[i], SENSOR_DISABLED); if (status != SENSOR_ENABLED) cl_data->sensor_sts[i] = SENSOR_DISABLED; - dev_dbg(dev, "suspend sid 0x%x status 0x%x\n", - cl_data->sensor_idx[i], cl_data->sensor_sts[i]); + dev_dbg(dev, "suspend sid 0x%x (%s) status 0x%x\n", + cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]), + cl_data->sensor_sts[i]); } } diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h index 97b99861fae2..8c760526132a 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h +++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h @@ -39,6 +39,9 @@ #define AMD_SFH_IDLE_LOOP 200 +#define SENSOR_DISCOVERY_STATUS_MASK GENMASK(5, 3) +#define SENSOR_DISCOVERY_STATUS_SHIFT 3 + /* SFH Command register */ union sfh_cmd_base { u32 ul; @@ -135,6 +138,7 @@ int amd_sfh_hid_client_deinit(struct amd_mp2_dev *privdata); u32 amd_sfh_wait_for_response(struct amd_mp2_dev *mp2, u8 sid, u32 sensor_sts); void amd_mp2_suspend(struct amd_mp2_dev *mp2); void amd_mp2_resume(struct amd_mp2_dev *mp2); +const char *get_sensor_name(int idx); struct amd_mp2_ops { void (*start)(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info info); @@ -143,5 +147,6 @@ struct amd_mp2_ops { int (*response)(struct amd_mp2_dev *mp2, u8 sid, u32 sensor_sts); void (*clear_intr)(struct amd_mp2_dev *privdata); int (*init_intr)(struct amd_mp2_dev *privdata); + int (*discovery_status)(struct amd_mp2_dev *privdata); }; #endif diff --git a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h index 8d97ca0f9b52..697f2791ea9c 100644 --- a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h +++ b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h @@ -179,7 +179,7 @@ static const u8 accel3_report_descriptor[] = { 0xC0 /* HID end collection */ }; -const u8 gyro3_report_descriptor[] = { +static const u8 gyro3_report_descriptor[] = { 0x05, 0x20, /* Usage page */ 0x09, 0x76, /* Motion type Gyro3D */ 0xA1, 0x00, /* HID Collection (Physical) */ @@ -340,7 +340,7 @@ const u8 gyro3_report_descriptor[] = { 0xC0, /* HID end collection */ }; -const u8 comp3_report_descriptor[] = { +static const u8 comp3_report_descriptor[] = { 0x05, 0x20, /* Usage page */ 0x09, 0x83, /* Motion type Orientation compass 3D */ 0xA1, 0x00, /* HID Collection (Physical) */ @@ -512,7 +512,7 @@ const u8 comp3_report_descriptor[] = { 0xC0 /* HID end collection */ }; -const u8 als_report_descriptor[] = { +static const u8 als_report_descriptor[] = { 0x05, 0x20, /* HID usage page sensor */ 0x09, 0x41, /* HID usage sensor type Ambientlight */ 0xA1, 0x00, /* HID Collection (Physical) */ diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 0cf35caee9fa..42a568902f49 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "hid-ids.h" @@ -35,16 +36,17 @@ #define APPLE_NUMLOCK_EMULATION BIT(8) #define APPLE_RDESC_BATTERY BIT(9) #define APPLE_BACKLIGHT_CTL BIT(10) +#define APPLE_IS_KEYCHRON BIT(11) #define APPLE_FLAG_FKEY 0x01 #define HID_COUNTRY_INTERNATIONAL_ISO 13 #define APPLE_BATTERY_TIMEOUT_MS 60000 -static unsigned int fnmode = 1; +static unsigned int fnmode = 3; module_param(fnmode, uint, 0644); MODULE_PARM_DESC(fnmode, "Mode of fn key on Apple keyboards (0 = disabled, " - "[1] = fkeyslast, 2 = fkeysfirst)"); + "1 = fkeyslast, 2 = fkeysfirst, [3] = auto)"); static int iso_layout = -1; module_param(iso_layout, int, 0644); @@ -349,6 +351,7 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, const struct apple_key_translation *trans, *table; bool do_translate; u16 code = 0; + unsigned int real_fnmode; u16 fn_keycode = (swap_fn_leftctrl) ? (KEY_LEFTCTRL) : (KEY_FN); @@ -359,7 +362,13 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, return 1; } - if (fnmode) { + if (fnmode == 3) { + real_fnmode = (asc->quirks & APPLE_IS_KEYCHRON) ? 2 : 1; + } else { + real_fnmode = fnmode; + } + + if (real_fnmode) { if (hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI || hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO || hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS || @@ -406,7 +415,7 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, if (!code) { if (trans->flags & APPLE_FLAG_FKEY) { - switch (fnmode) { + switch (real_fnmode) { case 1: do_translate = !asc->fn_on; break; @@ -660,6 +669,11 @@ static int apple_input_configured(struct hid_device *hdev, asc->quirks &= ~APPLE_HAS_FN; } + if (strncmp(hdev->name, "Keychron", 8) == 0) { + hid_info(hdev, "Keychron keyboard detected; function keys will default to fnmode=2 behavior\n"); + asc->quirks |= APPLE_IS_KEYCHRON; + } + return 0; } diff --git a/drivers/hid/hid-bigbenff.c b/drivers/hid/hid-bigbenff.c index 74ad8bf98bfd..e8c5e3ac9fff 100644 --- a/drivers/hid/hid-bigbenff.c +++ b/drivers/hid/hid-bigbenff.c @@ -347,6 +347,12 @@ static int bigben_probe(struct hid_device *hid, bigben->report = list_entry(report_list->next, struct hid_report, list); + if (list_empty(&hid->inputs)) { + hid_err(hid, "no inputs found\n"); + error = -ENODEV; + goto error_hw_stop; + } + hidinput = list_first_entry(&hid->inputs, struct hid_input, list); set_bit(FF_RUMBLE, hidinput->input->ffbit); diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index db925794fbe6..00154a1cd2d8 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2222,6 +2222,10 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) case BUS_VIRTUAL: bus = "VIRTUAL"; break; + case BUS_INTEL_ISHTP: + case BUS_AMD_SFH: + bus = "SENSOR HUB"; + break; default: bus = ""; } diff --git a/drivers/hid/hid-elan.c b/drivers/hid/hid-elan.c index 3091355d48df..8e4a5528e25d 100644 --- a/drivers/hid/hid-elan.c +++ b/drivers/hid/hid-elan.c @@ -188,7 +188,6 @@ static int elan_input_configured(struct hid_device *hdev, struct hid_input *hi) ret = input_mt_init_slots(input, ELAN_MAX_FINGERS, INPUT_MT_POINTER); if (ret) { hid_err(hdev, "Failed to init elan MT slots: %d\n", ret); - input_free_device(input); return ret; } @@ -200,7 +199,6 @@ static int elan_input_configured(struct hid_device *hdev, struct hid_input *hi) hid_err(hdev, "Failed to register elan input device: %d\n", ret); input_mt_destroy_slots(input); - input_free_device(input); return ret; } diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 053853a891c5..d9eb676abe96 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -761,13 +761,16 @@ #define USB_VENDOR_ID_LENOVO 0x17ef #define USB_DEVICE_ID_LENOVO_TPKBD 0x6009 #define USB_DEVICE_ID_LENOVO_CUSBKBD 0x6047 +#define USB_DEVICE_ID_LENOVO_TPIIUSBKBD 0x60ee #define USB_DEVICE_ID_LENOVO_CBTKBD 0x6048 +#define USB_DEVICE_ID_LENOVO_TPIIBTKBD 0x60e1 #define USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL 0x6049 #define USB_DEVICE_ID_LENOVO_TP10UBKBD 0x6062 #define USB_DEVICE_ID_LENOVO_TPPRODOCK 0x6067 #define USB_DEVICE_ID_LENOVO_X1_COVER 0x6085 #define USB_DEVICE_ID_LENOVO_X1_TAB 0x60a3 #define USB_DEVICE_ID_LENOVO_X1_TAB3 0x60b5 +#define USB_DEVICE_ID_LENOVO_X12_TAB 0x60fe #define USB_DEVICE_ID_LENOVO_OPTICAL_USB_MOUSE_600E 0x600e #define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_608D 0x608d #define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6019 0x6019 @@ -868,6 +871,9 @@ #define USB_VENDOR_ID_MCS 0x16d0 #define USB_DEVICE_ID_MCS_GAMEPADBLOCK 0x0bcc +#define USB_VENDOR_MEGAWORLD 0x07b5 +#define USB_DEVICE_ID_MEGAWORLD_GAMEPAD 0x0312 + #define USB_VENDOR_ID_MGE 0x0463 #define USB_DEVICE_ID_MGE_UPS 0xffff #define USB_DEVICE_ID_MGE_UPS1 0x0001 @@ -1272,6 +1278,7 @@ #define USB_DEVICE_ID_UGEE_XPPEN_TABLET_G540 0x0075 #define USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640 0x0094 #define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01 0x0042 +#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06 0x0078 #define USB_DEVICE_ID_UGEE_TABLET_G5 0x0074 #define USB_DEVICE_ID_UGEE_TABLET_EX07S 0x0071 #define USB_DEVICE_ID_UGEE_TABLET_RAINBOW_CV720 0x0055 diff --git a/drivers/hid/hid-kye.c b/drivers/hid/hid-kye.c index f46616390a98..da903138eee4 100644 --- a/drivers/hid/hid-kye.c +++ b/drivers/hid/hid-kye.c @@ -33,7 +33,7 @@ static __u8 easypen_i405x_rdesc_fixed[] = { 0xB1, 0x02, /* Feature (Variable), */ 0xC0, /* End Collection, */ 0x05, 0x0D, /* Usage Page (Digitizer), */ - 0x09, 0x02, /* Usage (Pen), */ + 0x09, 0x01, /* Usage (Digitizer), */ 0xA1, 0x01, /* Collection (Application), */ 0x85, 0x10, /* Report ID (16), */ 0x09, 0x20, /* Usage (Stylus), */ @@ -91,7 +91,7 @@ static __u8 mousepen_i608x_rdesc_fixed[] = { 0xB1, 0x02, /* Feature (Variable), */ 0xC0, /* End Collection, */ 0x05, 0x0D, /* Usage Page (Digitizer), */ - 0x09, 0x02, /* Usage (Pen), */ + 0x09, 0x01, /* Usage (Digitizer), */ 0xA1, 0x01, /* Collection (Application), */ 0x85, 0x10, /* Report ID (16), */ 0x09, 0x20, /* Usage (Stylus), */ @@ -190,7 +190,7 @@ static __u8 mousepen_i608x_v2_rdesc_fixed[] = { 0xB1, 0x02, /* Feature (Variable), */ 0xC0, /* End Collection, */ 0x05, 0x0D, /* Usage Page (Digitizer), */ - 0x09, 0x02, /* Usage (Pen), */ + 0x09, 0x01, /* Usage (Digitizer), */ 0xA1, 0x01, /* Collection (Application), */ 0x85, 0x10, /* Report ID (16), */ 0x09, 0x20, /* Usage (Stylus), */ @@ -289,7 +289,7 @@ static __u8 easypen_m610x_rdesc_fixed[] = { 0xB1, 0x02, /* Feature (Variable), */ 0xC0, /* End Collection, */ 0x05, 0x0D, /* Usage Page (Digitizer), */ - 0x09, 0x02, /* Usage (Pen), */ + 0x09, 0x01, /* Usage (Digitizer), */ 0xA1, 0x01, /* Collection (Application), */ 0x85, 0x10, /* Report ID (16), */ 0x09, 0x20, /* Usage (Stylus), */ @@ -368,7 +368,7 @@ static __u8 pensketch_m912_rdesc_fixed[] = { 0xB1, 0x02, /* Feature (Variable), */ 0xC0, /* End Collection, */ 0x05, 0x0D, /* Usage Page (Digitizer), */ - 0x09, 0x02, /* Usage (Pen), */ + 0x09, 0x01, /* Usage (Digitizer), */ 0xA1, 0x01, /* Collection (Application), */ 0x85, 0x10, /* Report ID (16), */ 0x09, 0x20, /* Usage (Stylus), */ @@ -497,7 +497,7 @@ static __u8 easypen_m406xe_rdesc_fixed[] = { 0xB1, 0x02, /* Feature (Variable), */ 0xC0, /* End Collection, */ 0x05, 0x0D, /* Usage Page (Digitizer), */ - 0x09, 0x02, /* Usage (Pen), */ + 0x09, 0x01, /* Usage (Digitizer), */ 0xA1, 0x01, /* Collection (Application), */ 0x85, 0x10, /* Report ID (16), */ 0x09, 0x20, /* Usage (Stylus), */ diff --git a/drivers/hid/hid-led.c b/drivers/hid/hid-led.c index c2c66ceca132..7d82f8d426bb 100644 --- a/drivers/hid/hid-led.c +++ b/drivers/hid/hid-led.c @@ -366,7 +366,7 @@ static const struct hidled_config hidled_configs[] = { .type = DREAM_CHEEKY, .name = "Dream Cheeky Webmail Notifier", .short_name = "dream_cheeky", - .max_brightness = 31, + .max_brightness = 63, .num_leds = 1, .report_size = 9, .report_type = RAW_REQUEST, diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index 93b1f935e526..9dabd6323234 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -4,6 +4,7 @@ * - ThinkPad USB Keyboard with TrackPoint (tpkbd) * - ThinkPad Compact Bluetooth Keyboard with TrackPoint (cptkbd) * - ThinkPad Compact USB Keyboard with TrackPoint (cptkbd) + * - ThinkPad TrackPoint Keyboard II USB/Bluetooth (cptkbd/tpIIkbd) * * Copyright (c) 2012 Bernhard Seibold * Copyright (c) 2014 Jamie Lentin @@ -110,6 +111,23 @@ static const __u8 lenovo_pro_dock_need_fixup_collection[] = { 0x2a, 0xff, 0xff, /* Usage Maximum (65535) */ }; +/* Broken ThinkPad TrackPoint II collection (Bluetooth mode) */ +static const __u8 lenovo_tpIIbtkbd_need_fixup_collection[] = { + 0x06, 0x00, 0xFF, /* Usage Page (Vendor Defined 0xFF00) */ + 0x09, 0x01, /* Usage (0x01) */ + 0xA1, 0x01, /* Collection (Application) */ + 0x85, 0x05, /* Report ID (5) */ + 0x1A, 0xF1, 0x00, /* Usage Minimum (0xF1) */ + 0x2A, 0xFC, 0x00, /* Usage Maximum (0xFC) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0x01, /* Logical Maximum (1) */ + 0x75, 0x01, /* Report Size (1) */ + 0x95, 0x0D, /* Report Count (13) */ + 0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ + 0x95, 0x03, /* Report Count (3) */ + 0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ +}; + static __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { @@ -126,6 +144,19 @@ static __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc, rdesc[152] = 0x00; } break; + case USB_DEVICE_ID_LENOVO_TPIIBTKBD: + if (*rsize >= 263 && + memcmp(&rdesc[234], lenovo_tpIIbtkbd_need_fixup_collection, + sizeof(lenovo_tpIIbtkbd_need_fixup_collection)) == 0) { + rdesc[244] = 0x00; /* usage minimum = 0x00 */ + rdesc[247] = 0xff; /* usage maximum = 0xff */ + rdesc[252] = 0xff; /* logical maximum = 0xff */ + rdesc[254] = 0x08; /* report size = 0x08 */ + rdesc[256] = 0x01; /* report count = 0x01 */ + rdesc[258] = 0x00; /* input = 0x00 */ + rdesc[260] = 0x01; /* report count (2) = 0x01 */ + } + break; } return rdesc; } @@ -217,6 +248,101 @@ static int lenovo_input_mapping_cptkbd(struct hid_device *hdev, return 0; } +static int lenovo_input_mapping_tpIIkbd(struct hid_device *hdev, + struct hid_input *hi, struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, int *max) +{ + /* + * 0xff0a0000 = USB, HID_UP_MSVENDOR = BT. + * + * In BT mode, there are two HID_UP_MSVENDOR pages. + * Use only the page that contains report ID == 5. + */ + if (((usage->hid & HID_USAGE_PAGE) == 0xff0a0000 || + (usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR) && + field->report->id == 5) { + switch (usage->hid & HID_USAGE) { + case 0x00bb: /* Fn-F4: Mic mute */ + map_key_clear(LENOVO_KEY_MICMUTE); + return 1; + case 0x00c3: /* Fn-F5: Brightness down */ + map_key_clear(KEY_BRIGHTNESSDOWN); + return 1; + case 0x00c4: /* Fn-F6: Brightness up */ + map_key_clear(KEY_BRIGHTNESSUP); + return 1; + case 0x00c1: /* Fn-F8: Notification center */ + map_key_clear(KEY_NOTIFICATION_CENTER); + return 1; + case 0x00bc: /* Fn-F9: Control panel */ + map_key_clear(KEY_CONFIG); + return 1; + case 0x00b6: /* Fn-F10: Bluetooth */ + map_key_clear(KEY_BLUETOOTH); + return 1; + case 0x00b7: /* Fn-F11: Keyboard config */ + map_key_clear(KEY_KEYBOARD); + return 1; + case 0x00b8: /* Fn-F12: User function */ + map_key_clear(KEY_PROG1); + return 1; + case 0x00b9: /* Fn-PrtSc: Snipping tool */ + map_key_clear(KEY_SELECTIVE_SCREENSHOT); + return 1; + case 0x00b5: /* Fn-Esc: Fn-lock toggle */ + map_key_clear(KEY_FN_ESC); + return 1; + } + } + + if ((usage->hid & HID_USAGE_PAGE) == 0xffa00000) { + switch (usage->hid & HID_USAGE) { + case 0x00fb: /* Middle mouse (in native USB mode) */ + map_key_clear(BTN_MIDDLE); + return 1; + } + } + + if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR && + field->report->id == 21) { + switch (usage->hid & HID_USAGE) { + case 0x0004: /* Middle mouse (in native Bluetooth mode) */ + map_key_clear(BTN_MIDDLE); + return 1; + } + } + + /* Compatibility middle/wheel mappings should be ignored */ + if (usage->hid == HID_GD_WHEEL) + return -1; + if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON && + (usage->hid & HID_USAGE) == 0x003) + return -1; + if ((usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER && + (usage->hid & HID_USAGE) == 0x238) + return -1; + + /* Map wheel emulation reports: 0xff10 */ + if ((usage->hid & HID_USAGE_PAGE) == 0xff100000) { + field->flags |= HID_MAIN_ITEM_RELATIVE | HID_MAIN_ITEM_VARIABLE; + field->logical_minimum = -127; + field->logical_maximum = 127; + + switch (usage->hid & HID_USAGE) { + case 0x0000: + hid_map_usage(hi, usage, bit, max, EV_REL, REL_HWHEEL); + return 1; + case 0x0001: + hid_map_usage(hi, usage, bit, max, EV_REL, REL_WHEEL); + return 1; + default: + return -1; + } + } + + return 0; +} + static int lenovo_input_mapping_scrollpoint(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) @@ -326,6 +452,10 @@ static int lenovo_input_mapping(struct hid_device *hdev, case USB_DEVICE_ID_LENOVO_CBTKBD: return lenovo_input_mapping_cptkbd(hdev, hi, field, usage, bit, max); + case USB_DEVICE_ID_LENOVO_TPIIUSBKBD: + case USB_DEVICE_ID_LENOVO_TPIIBTKBD: + return lenovo_input_mapping_tpIIkbd(hdev, hi, field, + usage, bit, max); case USB_DEVICE_ID_IBM_SCROLLPOINT_III: case USB_DEVICE_ID_IBM_SCROLLPOINT_PRO: case USB_DEVICE_ID_IBM_SCROLLPOINT_OPTICAL: @@ -357,16 +487,23 @@ static int lenovo_send_cmd_cptkbd(struct hid_device *hdev, if (!buf) return -ENOMEM; + /* + * Feature report 0x13 is used for USB, + * output report 0x18 is used for Bluetooth. + * buf[0] is ignored by hid_hw_raw_request. + */ buf[0] = 0x18; buf[1] = byte2; buf[2] = byte3; switch (hdev->product) { case USB_DEVICE_ID_LENOVO_CUSBKBD: + case USB_DEVICE_ID_LENOVO_TPIIUSBKBD: ret = hid_hw_raw_request(hdev, 0x13, buf, 3, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); break; case USB_DEVICE_ID_LENOVO_CBTKBD: + case USB_DEVICE_ID_LENOVO_TPIIBTKBD: ret = hid_hw_output_report(hdev, buf, 3); break; default: @@ -422,6 +559,8 @@ static ssize_t attr_fn_lock_store(struct device *dev, switch (hdev->product) { case USB_DEVICE_ID_LENOVO_CUSBKBD: case USB_DEVICE_ID_LENOVO_CBTKBD: + case USB_DEVICE_ID_LENOVO_TPIIUSBKBD: + case USB_DEVICE_ID_LENOVO_TPIIBTKBD: lenovo_features_set_cptkbd(hdev); break; case USB_DEVICE_ID_LENOVO_TP10UBKBD: @@ -556,6 +695,15 @@ static int lenovo_event_cptkbd(struct hid_device *hdev, return 1; } + if (usage->type == EV_KEY && usage->code == KEY_FN_ESC && value == 1) { + /* + * The user has toggled the Fn-lock state. Toggle our own + * cached value of it and sync our value to the keyboard to + * ensure things are in sync (the syncing should be a no-op). + */ + cptkbd_data->fn_lock = !cptkbd_data->fn_lock; + } + return 0; } @@ -568,6 +716,8 @@ static int lenovo_event(struct hid_device *hdev, struct hid_field *field, switch (hdev->product) { case USB_DEVICE_ID_LENOVO_CUSBKBD: case USB_DEVICE_ID_LENOVO_CBTKBD: + case USB_DEVICE_ID_LENOVO_TPIIUSBKBD: + case USB_DEVICE_ID_LENOVO_TPIIBTKBD: return lenovo_event_cptkbd(hdev, field, usage, value); case USB_DEVICE_ID_LENOVO_TP10UBKBD: case USB_DEVICE_ID_LENOVO_X1_TAB: @@ -960,8 +1110,9 @@ static int lenovo_probe_cptkbd(struct hid_device *hdev) struct lenovo_drvdata *cptkbd_data; /* All the custom action happens on the USBMOUSE device for USB */ - if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD - && hdev->type != HID_TYPE_USBMOUSE) { + if (((hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD) || + (hdev->product == USB_DEVICE_ID_LENOVO_TPIIUSBKBD)) && + hdev->type != HID_TYPE_USBMOUSE) { hid_dbg(hdev, "Ignoring keyboard half of device\n"); return 0; } @@ -977,11 +1128,14 @@ static int lenovo_probe_cptkbd(struct hid_device *hdev) /* * Tell the keyboard a driver understands it, and turn F7, F9, F11 into - * regular keys + * regular keys (Compact only) */ - ret = lenovo_send_cmd_cptkbd(hdev, 0x01, 0x03); - if (ret) - hid_warn(hdev, "Failed to switch F7/9/11 mode: %d\n", ret); + if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD || + hdev->product == USB_DEVICE_ID_LENOVO_CBTKBD) { + ret = lenovo_send_cmd_cptkbd(hdev, 0x01, 0x03); + if (ret) + hid_warn(hdev, "Failed to switch F7/9/11 mode: %d\n", ret); + } /* Switch middle button to native mode */ ret = lenovo_send_cmd_cptkbd(hdev, 0x09, 0x01); @@ -1088,6 +1242,8 @@ static int lenovo_probe(struct hid_device *hdev, break; case USB_DEVICE_ID_LENOVO_CUSBKBD: case USB_DEVICE_ID_LENOVO_CBTKBD: + case USB_DEVICE_ID_LENOVO_TPIIUSBKBD: + case USB_DEVICE_ID_LENOVO_TPIIBTKBD: ret = lenovo_probe_cptkbd(hdev); break; case USB_DEVICE_ID_LENOVO_TP10UBKBD: @@ -1154,6 +1310,8 @@ static void lenovo_remove(struct hid_device *hdev) break; case USB_DEVICE_ID_LENOVO_CUSBKBD: case USB_DEVICE_ID_LENOVO_CBTKBD: + case USB_DEVICE_ID_LENOVO_TPIIUSBKBD: + case USB_DEVICE_ID_LENOVO_TPIIBTKBD: lenovo_remove_cptkbd(hdev); break; case USB_DEVICE_ID_LENOVO_TP10UBKBD: @@ -1172,6 +1330,8 @@ static int lenovo_input_configured(struct hid_device *hdev, case USB_DEVICE_ID_LENOVO_TPKBD: case USB_DEVICE_ID_LENOVO_CUSBKBD: case USB_DEVICE_ID_LENOVO_CBTKBD: + case USB_DEVICE_ID_LENOVO_TPIIUSBKBD: + case USB_DEVICE_ID_LENOVO_TPIIBTKBD: if (test_bit(EV_REL, hi->input->evbit)) { /* set only for trackpoint device */ __set_bit(INPUT_PROP_POINTER, hi->input->propbit); @@ -1188,7 +1348,9 @@ static int lenovo_input_configured(struct hid_device *hdev, static const struct hid_device_id lenovo_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) }, { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPIIUSBKBD) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPIIBTKBD) }, { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPPRODOCK) }, { HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_III) }, { HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_PRO) }, diff --git a/drivers/hid/hid-megaworld.c b/drivers/hid/hid-megaworld.c new file mode 100644 index 000000000000..599657863cb9 --- /dev/null +++ b/drivers/hid/hid-megaworld.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Vibration support for Mega World controllers + * + * Copyright 2022 Frank Zago + * + * Derived from hid-zpff.c: + * Copyright (c) 2005, 2006 Anssi Hannula + */ + +#include +#include +#include +#include + +#include "hid-ids.h" + +struct mwctrl_device { + struct hid_report *report; + s32 *weak; + s32 *strong; +}; + +static int mwctrl_play(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct mwctrl_device *mwctrl = data; + + *mwctrl->strong = effect->u.rumble.strong_magnitude >> 8; + *mwctrl->weak = effect->u.rumble.weak_magnitude >> 8; + + hid_hw_request(hid, mwctrl->report, HID_REQ_SET_REPORT); + + return 0; +} + +static int mwctrl_init(struct hid_device *hid) +{ + struct mwctrl_device *mwctrl; + struct hid_report *report; + struct hid_input *hidinput; + struct input_dev *dev; + int error; + int i; + + if (list_empty(&hid->inputs)) { + hid_err(hid, "no inputs found\n"); + return -ENODEV; + } + hidinput = list_entry(hid->inputs.next, struct hid_input, list); + dev = hidinput->input; + + for (i = 0; i < 4; i++) { + report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, i, 1); + if (!report) + return -ENODEV; + } + + mwctrl = kzalloc(sizeof(struct mwctrl_device), GFP_KERNEL); + if (!mwctrl) + return -ENOMEM; + + set_bit(FF_RUMBLE, dev->ffbit); + + error = input_ff_create_memless(dev, mwctrl, mwctrl_play); + if (error) { + kfree(mwctrl); + return error; + } + + mwctrl->report = report; + + /* Field 0 is always 2, and field 1 is always 0. The original + * windows driver has a 5 bytes command, where the 5th byte is + * a repeat of the 3rd byte, however the device has only 4 + * fields. It could be a bug in the driver, or there is a + * different device that needs it. + */ + report->field[0]->value[0] = 0x02; + + mwctrl->strong = &report->field[2]->value[0]; + mwctrl->weak = &report->field[3]->value[0]; + + return 0; +} + +static int mwctrl_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "parse failed\n"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); + if (ret) { + hid_err(hdev, "hw start failed\n"); + return ret; + } + + ret = mwctrl_init(hdev); + if (ret) + hid_hw_stop(hdev); + + return ret; +} + +static const struct hid_device_id mwctrl_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_MEGAWORLD, + USB_DEVICE_ID_MEGAWORLD_GAMEPAD) }, + { } +}; +MODULE_DEVICE_TABLE(hid, mwctrl_devices); + +static struct hid_driver mwctrl_driver = { + .name = "megaworld", + .id_table = mwctrl_devices, + .probe = mwctrl_probe, +}; +module_hid_driver(mwctrl_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 99eabfb4145b..6bb3890b0f2c 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -2034,6 +2034,12 @@ static const struct hid_device_id mt_devices[] = { USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X1_TAB3) }, + /* Lenovo X12 TAB Gen 1 */ + { .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT, + HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, + USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_X12_TAB) }, + /* MosArt panels */ { .driver_data = MT_CLS_CONFIDENCE_MINUS_ONE, MT_USB_DEVICE(USB_VENDOR_ID_ASUS, @@ -2178,6 +2184,9 @@ static const struct hid_device_id mt_devices[] = { { .driver_data = MT_CLS_GOOGLE, HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_TOUCH_ROSE) }, + { .driver_data = MT_CLS_GOOGLE, + HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, USB_VENDOR_ID_GOOGLE, + USB_DEVICE_ID_GOOGLE_WHISKERS) }, /* Generic MT device */ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH, HID_ANY_ID, HID_ANY_ID) }, diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index 05147f2d7564..c0fe66e50c58 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -81,6 +81,24 @@ static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc, return rdesc; } +static int uclogic_input_mapping(struct hid_device *hdev, + struct hid_input *hi, + struct hid_field *field, + struct hid_usage *usage, + unsigned long **bit, + int *max) +{ + struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); + struct uclogic_params *params = &drvdata->params; + + /* Discard invalid pen usages */ + if (params->pen.usage_invalid && (field->application == HID_DG_PEN)) + return -1; + + /* Let hid-core decide what to do */ + return 0; +} + static int uclogic_input_configured(struct hid_device *hdev, struct hid_input *hi) { @@ -90,6 +108,8 @@ static int uclogic_input_configured(struct hid_device *hdev, const char *suffix = NULL; struct hid_field *field; size_t len; + size_t i; + const struct uclogic_params_frame *frame; /* no report associated (HID_QUIRK_MULTI_INPUT not set) */ if (!hi->report) @@ -104,27 +124,44 @@ static int uclogic_input_configured(struct hid_device *hdev, drvdata->pen_input = hi->input; } - field = hi->report->field[0]; + /* If it's one of the frame devices */ + for (i = 0; i < ARRAY_SIZE(params->frame_list); i++) { + frame = ¶ms->frame_list[i]; + if (hi->report->id == frame->id) { + /* Assign custom suffix, if any */ + suffix = frame->suffix; + /* + * Disable EV_MSC reports for touch ring interfaces to + * make the Wacom driver pickup touch ring extents + */ + if (frame->touch_byte > 0) + __clear_bit(EV_MSC, hi->input->evbit); + } + } - switch (field->application) { - case HID_GD_KEYBOARD: - suffix = "Keyboard"; - break; - case HID_GD_MOUSE: - suffix = "Mouse"; - break; - case HID_GD_KEYPAD: - suffix = "Pad"; - break; - case HID_DG_PEN: - suffix = "Pen"; - break; - case HID_CP_CONSUMER_CONTROL: - suffix = "Consumer Control"; - break; - case HID_GD_SYSTEM_CONTROL: - suffix = "System Control"; - break; + if (!suffix) { + field = hi->report->field[0]; + + switch (field->application) { + case HID_GD_KEYBOARD: + suffix = "Keyboard"; + break; + case HID_GD_MOUSE: + suffix = "Mouse"; + break; + case HID_GD_KEYPAD: + suffix = "Pad"; + break; + case HID_DG_PEN: + suffix = "Pen"; + break; + case HID_CP_CONSUMER_CONTROL: + suffix = "Consumer Control"; + break; + case HID_GD_SYSTEM_CONTROL: + suffix = "System Control"; + break; + } } if (suffix) { @@ -172,8 +209,8 @@ static int uclogic_probe(struct hid_device *hdev, goto failure; } params_initialized = true; - hid_dbg(hdev, "parameters:\n" UCLOGIC_PARAMS_FMT_STR, - UCLOGIC_PARAMS_FMT_ARGS(&drvdata->params)); + hid_dbg(hdev, "parameters:\n"); + uclogic_params_hid_dbg(hdev, &drvdata->params); if (drvdata->params.invalid) { hid_info(hdev, "interface is invalid, ignoring\n"); rc = -ENODEV; @@ -313,8 +350,15 @@ static int uclogic_raw_event_frame( /* If need to, and can, set pad device ID for Wacom drivers */ if (frame->dev_id_byte > 0 && frame->dev_id_byte < size) { - data[frame->dev_id_byte] = 0xf; + /* If we also have a touch ring and the finger left it */ + if (frame->touch_byte > 0 && frame->touch_byte < size && + data[frame->touch_byte] == 0) { + data[frame->dev_id_byte] = 0; + } else { + data[frame->dev_id_byte] = 0xf; + } } + /* If need to, and can, read rotary encoder state change */ if (frame->re_lsb > 0 && frame->re_lsb / 8 < size) { unsigned int byte = frame->re_lsb / 8; @@ -341,6 +385,26 @@ static int uclogic_raw_event_frame( drvdata->re_state = state; } + /* If need to, and can, transform the touch ring reports */ + if (frame->touch_byte > 0 && frame->touch_byte < size) { + __s8 value = data[frame->touch_byte]; + + if (value != 0) { + if (frame->touch_flip_at != 0) { + value = frame->touch_flip_at - value; + if (value <= 0) + value = frame->touch_max + value; + } + data[frame->touch_byte] = value - 1; + } + } + + /* If need to, and can, transform the bitmap dial reports */ + if (frame->bitmap_dial_byte > 0 && frame->bitmap_dial_byte < size) { + if (data[frame->bitmap_dial_byte] == 2) + data[frame->bitmap_dial_byte] = -1; + } + return 0; } @@ -457,6 +521,8 @@ static const struct hid_device_id uclogic_devices[] = { USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640) }, { HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UGEE, + USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06) }, { } }; MODULE_DEVICE_TABLE(hid, uclogic_devices); @@ -468,6 +534,7 @@ static struct hid_driver uclogic_driver = { .remove = uclogic_remove, .report_fixup = uclogic_report_fixup, .raw_event = uclogic_raw_event, + .input_mapping = uclogic_input_mapping, .input_configured = uclogic_input_configured, #ifdef CONFIG_PM .resume = uclogic_resume, diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index 5f50ceb875d6..db838f16282d 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -29,8 +29,8 @@ * Returns: * The string representing the type, or NULL if the type is unknown. */ -const char *uclogic_params_pen_inrange_to_str( - enum uclogic_params_pen_inrange inrange) +static const char *uclogic_params_pen_inrange_to_str( + enum uclogic_params_pen_inrange inrange) { switch (inrange) { case UCLOGIC_PARAMS_PEN_INRANGE_NORMAL: @@ -44,6 +44,91 @@ const char *uclogic_params_pen_inrange_to_str( } } +/** + * Dump tablet interface pen parameters with hid_dbg(), indented with one tab. + * + * @hdev: The HID device the pen parameters describe. + * @pen: The pen parameters to dump. + */ +static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev, + const struct uclogic_params_pen *pen) +{ + size_t i; + + hid_dbg(hdev, "\t.usage_invalid = %s\n", + (pen->usage_invalid ? "true" : "false")); + hid_dbg(hdev, "\t.desc_ptr = %p\n", pen->desc_ptr); + hid_dbg(hdev, "\t.desc_size = %u\n", pen->desc_size); + hid_dbg(hdev, "\t.id = %u\n", pen->id); + hid_dbg(hdev, "\t.subreport_list = {\n"); + for (i = 0; i < ARRAY_SIZE(pen->subreport_list); i++) { + hid_dbg(hdev, "\t\t{0x%02hhx, %hhu}%s\n", + pen->subreport_list[i].value, + pen->subreport_list[i].id, + i < (ARRAY_SIZE(pen->subreport_list) - 1) ? "," : ""); + } + hid_dbg(hdev, "\t}\n"); + hid_dbg(hdev, "\t.inrange = %s\n", + uclogic_params_pen_inrange_to_str(pen->inrange)); + hid_dbg(hdev, "\t.fragmented_hires = %s\n", + (pen->fragmented_hires ? "true" : "false")); + hid_dbg(hdev, "\t.tilt_y_flipped = %s\n", + (pen->tilt_y_flipped ? "true" : "false")); +} + +/** + * Dump tablet interface frame parameters with hid_dbg(), indented with two + * tabs. + * + * @hdev: The HID device the pen parameters describe. + * @frame: The frame parameters to dump. + */ +static void uclogic_params_frame_hid_dbg( + const struct hid_device *hdev, + const struct uclogic_params_frame *frame) +{ + hid_dbg(hdev, "\t\t.desc_ptr = %p\n", frame->desc_ptr); + hid_dbg(hdev, "\t\t.desc_size = %u\n", frame->desc_size); + hid_dbg(hdev, "\t\t.id = %u\n", frame->id); + hid_dbg(hdev, "\t\t.suffix = %s\n", frame->suffix); + hid_dbg(hdev, "\t\t.re_lsb = %u\n", frame->re_lsb); + hid_dbg(hdev, "\t\t.dev_id_byte = %u\n", frame->dev_id_byte); + hid_dbg(hdev, "\t\t.touch_byte = %u\n", frame->touch_byte); + hid_dbg(hdev, "\t\t.touch_max = %hhd\n", frame->touch_max); + hid_dbg(hdev, "\t\t.touch_flip_at = %hhd\n", + frame->touch_flip_at); + hid_dbg(hdev, "\t\t.bitmap_dial_byte = %u\n", + frame->bitmap_dial_byte); +} + +/** + * Dump tablet interface parameters with hid_dbg(). + * + * @hdev: The HID device the parameters describe. + * @params: The parameters to dump. + */ +void uclogic_params_hid_dbg(const struct hid_device *hdev, + const struct uclogic_params *params) +{ + size_t i; + + hid_dbg(hdev, ".invalid = %s\n", + params->invalid ? "true" : "false"); + hid_dbg(hdev, ".desc_ptr = %p\n", params->desc_ptr); + hid_dbg(hdev, ".desc_size = %u\n", params->desc_size); + hid_dbg(hdev, ".pen = {\n"); + uclogic_params_pen_hid_dbg(hdev, ¶ms->pen); + hid_dbg(hdev, "\t}\n"); + hid_dbg(hdev, ".frame_list = {\n"); + for (i = 0; i < ARRAY_SIZE(params->frame_list); i++) { + hid_dbg(hdev, "\t{\n"); + uclogic_params_frame_hid_dbg(hdev, ¶ms->frame_list[i]); + hid_dbg(hdev, "\t}%s\n", + i < (ARRAY_SIZE(params->frame_list) - 1) ? "," : ""); + } + hid_dbg(hdev, "}\n"); +} + /** * uclogic_params_get_str_desc - retrieve a string descriptor from a HID * device interface, putting it into a kmalloc-allocated buffer as is, without @@ -253,28 +338,45 @@ static s32 uclogic_params_get_le24(const void *p) * uclogic_params_pen_init_v2() - initialize tablet interface pen * input and retrieve its parameters from the device, using v2 protocol. * - * @pen: Pointer to the pen parameters to initialize (to be - * cleaned up with uclogic_params_pen_cleanup()). Not modified in - * case of error, or if parameters are not found. Cannot be NULL. - * @pfound: Location for a flag which is set to true if the parameters - * were found, and to false if not (e.g. device was - * incompatible). Not modified in case of error. Cannot be NULL. - * @hdev: The HID device of the tablet interface to initialize and get - * parameters from. Cannot be NULL. + * @pen: Pointer to the pen parameters to initialize (to be + * cleaned up with uclogic_params_pen_cleanup()). Not + * modified in case of error, or if parameters are not + * found. Cannot be NULL. + * @pfound: Location for a flag which is set to true if the + * parameters were found, and to false if not (e.g. + * device was incompatible). Not modified in case of + * error. Cannot be NULL. + * @pparams_ptr: Location for a kmalloc'ed pointer to the retrieved raw + * parameters, which could be used to identify the tablet + * to some extent. Should be freed with kfree after use. + * NULL, if not needed. Not modified in case of error. + * Only set if *pfound is set to true. + * @pparams_len: Location for the length of the retrieved raw + * parameters. NULL, if not needed. Not modified in case + * of error. Only set if *pfound is set to true. + * @hdev: The HID device of the tablet interface to initialize + * and get parameters from. Cannot be NULL. * * Returns: * Zero, if successful. A negative errno code on error. */ static int uclogic_params_pen_init_v2(struct uclogic_params_pen *pen, bool *pfound, + __u8 **pparams_ptr, + size_t *pparams_len, struct hid_device *hdev) { int rc; bool found = false; - /* Buffer for (part of) the string descriptor */ + /* Buffer for (part of) the parameter string descriptor */ __u8 *buf = NULL; - /* Descriptor length required */ - const int len = 18; + /* Parameter string descriptor required length */ + const int params_len_min = 18; + /* Parameter string descriptor accepted length */ + const int params_len_max = 32; + /* Parameter string descriptor received length */ + int params_len; + size_t i; s32 resolution; /* Pen report descriptor template parameters */ s32 desc_params[UCLOGIC_RDESC_PEN_PH_ID_NUM]; @@ -292,7 +394,7 @@ static int uclogic_params_pen_init_v2(struct uclogic_params_pen *pen, * the Windows driver traffic. * NOTE: This enables fully-functional tablet mode. */ - rc = uclogic_params_get_str_desc(&buf, hdev, 200, len); + rc = uclogic_params_get_str_desc(&buf, hdev, 200, params_len_max); if (rc == -EPIPE) { hid_dbg(hdev, "string descriptor with pen parameters not found, assuming not compatible\n"); @@ -300,27 +402,28 @@ static int uclogic_params_pen_init_v2(struct uclogic_params_pen *pen, } else if (rc < 0) { hid_err(hdev, "failed retrieving pen parameters: %d\n", rc); goto cleanup; - } else if (rc != len) { + } else if (rc < params_len_min) { hid_dbg(hdev, - "string descriptor with pen parameters has invalid length (got %d, expected %d), assuming not compatible\n", - rc, len); + "string descriptor with pen parameters is too short (got %d, expected at least %d), assuming not compatible\n", + rc, params_len_min); + goto finish; + } + + params_len = rc; + + /* + * Check it's not just a catch-all UTF-16LE-encoded ASCII + * string (such as the model name) some tablets put into all + * unknown string descriptors. + */ + for (i = 2; + i < params_len && + (buf[i] >= 0x20 && buf[i] < 0x7f && buf[i + 1] == 0); + i += 2); + if (i >= params_len) { + hid_dbg(hdev, + "string descriptor with pen parameters seems to contain only text, assuming not compatible\n"); goto finish; - } else { - size_t i; - /* - * Check it's not just a catch-all UTF-16LE-encoded ASCII - * string (such as the model name) some tablets put into all - * unknown string descriptors. - */ - for (i = 2; - i < len && - (buf[i] >= 0x20 && buf[i] < 0x7f && buf[i + 1] == 0); - i += 2); - if (i >= len) { - hid_dbg(hdev, - "string descriptor with pen parameters seems to contain only text, assuming not compatible\n"); - goto finish; - } } /* @@ -344,8 +447,6 @@ static int uclogic_params_pen_init_v2(struct uclogic_params_pen *pen, desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] * 1000 / resolution; } - kfree(buf); - buf = NULL; /* * Generate pen report descriptor @@ -371,6 +472,13 @@ static int uclogic_params_pen_init_v2(struct uclogic_params_pen *pen, pen->fragmented_hires = true; pen->tilt_y_flipped = true; found = true; + if (pparams_ptr != NULL) { + *pparams_ptr = buf; + buf = NULL; + } + if (pparams_len != NULL) + *pparams_len = params_len; + finish: *pfound = found; rc = 0; @@ -700,6 +808,14 @@ static int uclogic_params_huion_init(struct uclogic_params *params, static const char transition_ver[] = "HUION_T153_160607"; char *ver_ptr = NULL; const size_t ver_len = sizeof(transition_ver) + 1; + __u8 *params_ptr = NULL; + size_t params_len = 0; + /* Parameters string descriptor of a model with touch ring (HS610) */ + const __u8 touch_ring_model_params_buf[] = { + 0x13, 0x03, 0x70, 0xC6, 0x00, 0x06, 0x7C, 0x00, + 0xFF, 0x1F, 0xD8, 0x13, 0x03, 0x0D, 0x10, 0x01, + 0x04, 0x3C, 0x3E + }; /* Check arguments */ if (params == NULL || hdev == NULL) { @@ -711,8 +827,13 @@ static int uclogic_params_huion_init(struct uclogic_params *params, iface = to_usb_interface(hdev->dev.parent); bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber; - /* If it's not a pen interface */ - if (bInterfaceNumber != 0) { + /* If it's a custom keyboard interface */ + if (bInterfaceNumber == 1) { + /* Keep everything intact, but mark pen usage invalid */ + p.pen.usage_invalid = true; + goto output; + /* Else, if it's not a pen interface */ + } else if (bInterfaceNumber != 0) { uclogic_params_init_invalid(&p); goto output; } @@ -738,29 +859,103 @@ static int uclogic_params_huion_init(struct uclogic_params *params, "transition firmware detected, not probing pen v2 parameters\n"); } else { /* Try to probe v2 pen parameters */ - rc = uclogic_params_pen_init_v2(&p.pen, &found, hdev); + rc = uclogic_params_pen_init_v2(&p.pen, &found, + ¶ms_ptr, ¶ms_len, + hdev); if (rc != 0) { hid_err(hdev, "failed probing pen v2 parameters: %d\n", rc); goto cleanup; } else if (found) { hid_dbg(hdev, "pen v2 parameters found\n"); - /* Create v2 frame parameters */ + /* Create v2 frame button parameters */ rc = uclogic_params_frame_init_with_desc( &p.frame_list[0], - uclogic_rdesc_v2_frame_arr, - uclogic_rdesc_v2_frame_size, - UCLOGIC_RDESC_V2_FRAME_ID); + uclogic_rdesc_v2_frame_buttons_arr, + uclogic_rdesc_v2_frame_buttons_size, + UCLOGIC_RDESC_V2_FRAME_BUTTONS_ID); if (rc != 0) { hid_err(hdev, - "failed creating v2 frame parameters: %d\n", + "failed creating v2 frame button parameters: %d\n", rc); goto cleanup; } - /* Link frame button subreports from pen reports */ + + /* Link from pen sub-report */ p.pen.subreport_list[0].value = 0xe0; p.pen.subreport_list[0].id = - UCLOGIC_RDESC_V2_FRAME_ID; + UCLOGIC_RDESC_V2_FRAME_BUTTONS_ID; + + /* If this is the model with touch ring */ + if (params_ptr != NULL && + params_len == sizeof(touch_ring_model_params_buf) && + memcmp(params_ptr, touch_ring_model_params_buf, + params_len) == 0) { + /* Create touch ring parameters */ + rc = uclogic_params_frame_init_with_desc( + &p.frame_list[1], + uclogic_rdesc_v2_frame_touch_ring_arr, + uclogic_rdesc_v2_frame_touch_ring_size, + UCLOGIC_RDESC_V2_FRAME_TOUCH_ID); + if (rc != 0) { + hid_err(hdev, + "failed creating v2 frame touch ring parameters: %d\n", + rc); + goto cleanup; + } + p.frame_list[1].suffix = "Touch Ring"; + p.frame_list[1].dev_id_byte = + UCLOGIC_RDESC_V2_FRAME_TOUCH_DEV_ID_BYTE; + p.frame_list[1].touch_byte = 5; + p.frame_list[1].touch_max = 12; + p.frame_list[1].touch_flip_at = 7; + } else { + /* Create touch strip parameters */ + rc = uclogic_params_frame_init_with_desc( + &p.frame_list[1], + uclogic_rdesc_v2_frame_touch_strip_arr, + uclogic_rdesc_v2_frame_touch_strip_size, + UCLOGIC_RDESC_V2_FRAME_TOUCH_ID); + if (rc != 0) { + hid_err(hdev, + "failed creating v2 frame touch strip parameters: %d\n", + rc); + goto cleanup; + } + p.frame_list[1].suffix = "Touch Strip"; + p.frame_list[1].dev_id_byte = + UCLOGIC_RDESC_V2_FRAME_TOUCH_DEV_ID_BYTE; + p.frame_list[1].touch_byte = 5; + p.frame_list[1].touch_max = 8; + } + + /* Link from pen sub-report */ + p.pen.subreport_list[1].value = 0xf0; + p.pen.subreport_list[1].id = + UCLOGIC_RDESC_V2_FRAME_TOUCH_ID; + + /* Create v2 frame dial parameters */ + rc = uclogic_params_frame_init_with_desc( + &p.frame_list[2], + uclogic_rdesc_v2_frame_dial_arr, + uclogic_rdesc_v2_frame_dial_size, + UCLOGIC_RDESC_V2_FRAME_DIAL_ID); + if (rc != 0) { + hid_err(hdev, + "failed creating v2 frame dial parameters: %d\n", + rc); + goto cleanup; + } + p.frame_list[2].suffix = "Dial"; + p.frame_list[2].dev_id_byte = + UCLOGIC_RDESC_V2_FRAME_DIAL_DEV_ID_BYTE; + p.frame_list[2].bitmap_dial_byte = 5; + + /* Link from pen sub-report */ + p.pen.subreport_list[2].value = 0xf1; + p.pen.subreport_list[2].id = + UCLOGIC_RDESC_V2_FRAME_DIAL_ID; + goto output; } hid_dbg(hdev, "pen v2 parameters not found\n"); @@ -801,6 +996,7 @@ output: memset(&p, 0, sizeof(p)); rc = 0; cleanup: + kfree(params_ptr); kfree(ver_ptr); uclogic_params_cleanup(&p); return rc; @@ -999,6 +1195,8 @@ int uclogic_params_init(struct uclogic_params *params, USB_DEVICE_ID_UGEE_XPPEN_TABLET_G540): case VID_PID(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640): + case VID_PID(USB_VENDOR_ID_UGEE, + USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06): case VID_PID(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_TABLET_RAINBOW_CV720): /* If this is the pen interface */ diff --git a/drivers/hid/hid-uclogic-params.h b/drivers/hid/hid-uclogic-params.h index 86f616dfbb53..5bef8daaa607 100644 --- a/drivers/hid/hid-uclogic-params.h +++ b/drivers/hid/hid-uclogic-params.h @@ -29,11 +29,6 @@ enum uclogic_params_pen_inrange { UCLOGIC_PARAMS_PEN_INRANGE_NONE, }; -/* Convert a pen in-range reporting type to a string */ -extern const char *uclogic_params_pen_inrange_to_str( - enum uclogic_params_pen_inrange inrange); - - /* * Pen report's subreport data. */ @@ -62,8 +57,13 @@ struct uclogic_params_pen_subreport { */ struct uclogic_params_pen { /* - * Pointer to report descriptor describing the inputs. - * Allocated with kmalloc. + * True if pen usage is invalid for this interface and should be + * ignored, false otherwise. + */ + bool usage_invalid; + /* + * Pointer to report descriptor part describing the pen inputs. + * Allocated with kmalloc. NULL if the part is not specified. */ __u8 *desc_ptr; /* @@ -73,8 +73,8 @@ struct uclogic_params_pen { unsigned int desc_size; /* Report ID, if reports should be tweaked, zero if not */ unsigned int id; - /* The list of subreports */ - struct uclogic_params_pen_subreport subreport_list[1]; + /* The list of subreports, only valid if "id" is not zero */ + struct uclogic_params_pen_subreport subreport_list[3]; /* Type of in-range reporting, only valid if "id" is not zero */ enum uclogic_params_pen_inrange inrange; /* @@ -101,8 +101,8 @@ struct uclogic_params_pen { */ struct uclogic_params_frame { /* - * Pointer to report descriptor describing the inputs. - * Allocated with kmalloc. + * Pointer to report descriptor part describing the frame inputs. + * Allocated with kmalloc. NULL if the part is not specified. */ __u8 *desc_ptr; /* @@ -114,6 +114,10 @@ struct uclogic_params_frame { * Report ID, if reports should be tweaked, zero if not. */ unsigned int id; + /* + * The suffix to add to the input device name, if not NULL. + */ + const char *suffix; /* * Number of the least-significant bit of the 2-bit state of a rotary * encoder, in the report. Cannot point to a 2-bit field crossing a @@ -123,10 +127,40 @@ struct uclogic_params_frame { /* * Offset of the Wacom-style device ID byte in the report, to be set * to pad device ID (0xf), for compatibility with Wacom drivers. Zero - * if no changes to the report should be made. Only valid if "id" is - * not zero. + * if no changes to the report should be made. The ID byte will be set + * to zero whenever the byte pointed by "touch_byte" is zero, if + * the latter is valid. Only valid if "id" is not zero. */ unsigned int dev_id_byte; + /* + * Offset of the touch ring/strip state byte, in the report. + * Zero if not present. If dev_id_byte is also valid and non-zero, + * then the device ID byte will be cleared when the byte pointed to by + * this offset is zero. Only valid if "id" is not zero. + */ + unsigned int touch_byte; + /* + * The value to anchor the reversed touch ring/strip reports at. + * I.e. one, if the reports should be flipped without offset. + * Zero if no reversal should be done. + * Only valid if "touch_byte" is valid and not zero. + */ + __s8 touch_flip_at; + /* + * Maximum value of the touch ring/strip report around which the value + * should be wrapped when flipping according to "touch_flip_at". + * The minimum valid value is considered to be one, with zero being + * out-of-proximity (finger lift) value. + * Only valid if "touch_flip_at" is valid and not zero. + */ + __s8 touch_max; + /* + * Offset of the bitmap dial byte, in the report. Zero if not present. + * Only valid if "id" is not zero. A bitmap dial sends reports with a + * dedicated bit per direction: 1 means clockwise rotation, 2 means + * counterclockwise, as opposed to the normal 1 and -1. + */ + unsigned int bitmap_dial_byte; }; /* @@ -156,7 +190,7 @@ struct uclogic_params { __u8 *desc_ptr; /* * Size of the common part of the replacement report descriptor. - * Only valid, if "desc_ptr" is not NULL. + * Only valid, if "desc_ptr" is valid and not NULL. */ unsigned int desc_size; /* @@ -168,50 +202,13 @@ struct uclogic_params { * The list of frame control parameters and optional report descriptor * parts. Only valid, if "invalid" is false. */ - struct uclogic_params_frame frame_list[1]; + struct uclogic_params_frame frame_list[3]; }; /* Initialize a tablet interface and discover its parameters */ extern int uclogic_params_init(struct uclogic_params *params, struct hid_device *hdev); -/* Tablet interface parameters *printf format string */ -#define UCLOGIC_PARAMS_FMT_STR \ - ".invalid = %s\n" \ - ".desc_ptr = %p\n" \ - ".desc_size = %u\n" \ - ".pen.desc_ptr = %p\n" \ - ".pen.desc_size = %u\n" \ - ".pen.id = %u\n" \ - ".pen.subreport_list[0] = {0x%02hhx, %hhu}\n" \ - ".pen.inrange = %s\n" \ - ".pen.fragmented_hires = %s\n" \ - ".pen.tilt_y_flipped = %s\n" \ - ".frame_list[0].desc_ptr = %p\n" \ - ".frame_list[0].desc_size = %u\n" \ - ".frame_list[0].id = %u\n" \ - ".frame_list[0].re_lsb = %u\n" \ - ".frame_list[0].dev_id_byte = %u\n" - -/* Tablet interface parameters *printf format arguments */ -#define UCLOGIC_PARAMS_FMT_ARGS(_params) \ - ((_params)->invalid ? "true" : "false"), \ - (_params)->desc_ptr, \ - (_params)->desc_size, \ - (_params)->pen.desc_ptr, \ - (_params)->pen.desc_size, \ - (_params)->pen.id, \ - (_params)->pen.subreport_list[0].value, \ - (_params)->pen.subreport_list[0].id, \ - uclogic_params_pen_inrange_to_str((_params)->pen.inrange), \ - ((_params)->pen.fragmented_hires ? "true" : "false"), \ - ((_params)->pen.tilt_y_flipped ? "true" : "false"), \ - (_params)->frame_list[0].desc_ptr, \ - (_params)->frame_list[0].desc_size, \ - (_params)->frame_list[0].id, \ - (_params)->frame_list[0].re_lsb, \ - (_params)->frame_list[0].dev_id_byte - /* Get a replacement report descriptor for a tablet's interface. */ extern int uclogic_params_get_desc(const struct uclogic_params *params, __u8 **pdesc, @@ -220,4 +217,8 @@ extern int uclogic_params_get_desc(const struct uclogic_params *params, /* Free resources used by tablet interface's parameters */ extern void uclogic_params_cleanup(struct uclogic_params *params); +/* Dump tablet interface parameters with hid_dbg() */ +extern void uclogic_params_hid_dbg(const struct hid_device *hdev, + const struct uclogic_params *params); + #endif /* _HID_UCLOGIC_PARAMS_H */ diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c index 04644d93bd11..13f9ce73f1b1 100644 --- a/drivers/hid/hid-uclogic-rdesc.c +++ b/drivers/hid/hid-uclogic-rdesc.c @@ -21,7 +21,7 @@ /* Fixed WP4030U report descriptor */ __u8 uclogic_rdesc_wp4030u_fixed_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ - 0x09, 0x02, /* Usage (Pen), */ + 0x09, 0x01, /* Usage (Digitizer), */ 0xA1, 0x01, /* Collection (Application), */ 0x85, 0x09, /* Report ID (9), */ 0x09, 0x20, /* Usage (Stylus), */ @@ -66,7 +66,7 @@ const size_t uclogic_rdesc_wp4030u_fixed_size = /* Fixed WP5540U report descriptor */ __u8 uclogic_rdesc_wp5540u_fixed_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ - 0x09, 0x02, /* Usage (Pen), */ + 0x09, 0x01, /* Usage (Digitizer), */ 0xA1, 0x01, /* Collection (Application), */ 0x85, 0x09, /* Report ID (9), */ 0x09, 0x20, /* Usage (Stylus), */ @@ -143,7 +143,7 @@ const size_t uclogic_rdesc_wp5540u_fixed_size = /* Fixed WP8060U report descriptor */ __u8 uclogic_rdesc_wp8060u_fixed_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ - 0x09, 0x02, /* Usage (Pen), */ + 0x09, 0x01, /* Usage (Digitizer), */ 0xA1, 0x01, /* Collection (Application), */ 0x85, 0x09, /* Report ID (9), */ 0x09, 0x20, /* Usage (Stylus), */ @@ -220,7 +220,7 @@ const size_t uclogic_rdesc_wp8060u_fixed_size = /* Fixed WP1062 report descriptor */ __u8 uclogic_rdesc_wp1062_fixed_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ - 0x09, 0x02, /* Usage (Pen), */ + 0x09, 0x01, /* Usage (Digitizer), */ 0xA1, 0x01, /* Collection (Application), */ 0x85, 0x09, /* Report ID (9), */ 0x09, 0x20, /* Usage (Stylus), */ @@ -268,7 +268,7 @@ const size_t uclogic_rdesc_wp1062_fixed_size = /* Fixed PF1209 report descriptor */ __u8 uclogic_rdesc_pf1209_fixed_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ - 0x09, 0x02, /* Usage (Pen), */ + 0x09, 0x01, /* Usage (Digitizer), */ 0xA1, 0x01, /* Collection (Application), */ 0x85, 0x09, /* Report ID (9), */ 0x09, 0x20, /* Usage (Stylus), */ @@ -345,7 +345,7 @@ const size_t uclogic_rdesc_pf1209_fixed_size = /* Fixed PID 0522 tablet report descriptor, interface 0 (stylus) */ __u8 uclogic_rdesc_twhl850_fixed0_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ - 0x09, 0x02, /* Usage (Pen), */ + 0x09, 0x01, /* Usage (Digitizer), */ 0xA1, 0x01, /* Collection (Application), */ 0x85, 0x09, /* Report ID (9), */ 0x09, 0x20, /* Usage (Stylus), */ @@ -457,7 +457,7 @@ const size_t uclogic_rdesc_twhl850_fixed2_size = /* Fixed TWHA60 report descriptor, interface 0 (stylus) */ __u8 uclogic_rdesc_twha60_fixed0_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ - 0x09, 0x02, /* Usage (Pen), */ + 0x09, 0x01, /* Usage (Digitizer), */ 0xA1, 0x01, /* Collection (Application), */ 0x85, 0x09, /* Report ID (9), */ 0x09, 0x20, /* Usage (Stylus), */ @@ -534,7 +534,7 @@ const size_t uclogic_rdesc_twha60_fixed1_size = /* Fixed report descriptor template for (tweaked) v1 pen reports */ const __u8 uclogic_rdesc_v1_pen_template_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ - 0x09, 0x02, /* Usage (Pen), */ + 0x09, 0x01, /* Usage (Digitizer), */ 0xA1, 0x01, /* Collection (Application), */ 0x85, 0x07, /* Report ID (7), */ 0x09, 0x20, /* Usage (Stylus), */ @@ -588,7 +588,7 @@ const size_t uclogic_rdesc_v1_pen_template_size = /* Fixed report descriptor template for (tweaked) v2 pen reports */ const __u8 uclogic_rdesc_v2_pen_template_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ - 0x09, 0x02, /* Usage (Pen), */ + 0x09, 0x01, /* Usage (Digitizer), */ 0xA1, 0x01, /* Collection (Application), */ 0x85, 0x08, /* Report ID (8), */ 0x09, 0x20, /* Usage (Stylus), */ @@ -652,12 +652,12 @@ const size_t uclogic_rdesc_v2_pen_template_size = sizeof(uclogic_rdesc_v2_pen_template_arr); /* - * Expand to the contents of a generic frame report descriptor. + * Expand to the contents of a generic frame buttons report descriptor. * * @_id: The report ID to use. * @_size: Size of the report to pad to, including report ID, bytes. */ -#define UCLOGIC_RDESC_FRAME_BYTES(_id, _size) \ +#define UCLOGIC_RDESC_FRAME_BUTTONS_BYTES(_id, _size) \ 0x05, 0x01, /* Usage Page (Desktop), */ \ 0x09, 0x07, /* Usage (Keypad), */ \ 0xA1, 0x01, /* Collection (Application), */ \ @@ -700,17 +700,164 @@ const size_t uclogic_rdesc_v2_pen_template_size = /* Fixed report descriptor for (tweaked) v1 frame reports */ const __u8 uclogic_rdesc_v1_frame_arr[] = { - UCLOGIC_RDESC_FRAME_BYTES(UCLOGIC_RDESC_V1_FRAME_ID, 8) + UCLOGIC_RDESC_FRAME_BUTTONS_BYTES(UCLOGIC_RDESC_V1_FRAME_ID, 8) }; const size_t uclogic_rdesc_v1_frame_size = sizeof(uclogic_rdesc_v1_frame_arr); -/* Fixed report descriptor for (tweaked) v2 frame reports */ -const __u8 uclogic_rdesc_v2_frame_arr[] = { - UCLOGIC_RDESC_FRAME_BYTES(UCLOGIC_RDESC_V2_FRAME_ID, 12) +/* Fixed report descriptor for (tweaked) v2 frame button reports */ +const __u8 uclogic_rdesc_v2_frame_buttons_arr[] = { + UCLOGIC_RDESC_FRAME_BUTTONS_BYTES(UCLOGIC_RDESC_V2_FRAME_BUTTONS_ID, + 12) }; -const size_t uclogic_rdesc_v2_frame_size = - sizeof(uclogic_rdesc_v2_frame_arr); +const size_t uclogic_rdesc_v2_frame_buttons_size = + sizeof(uclogic_rdesc_v2_frame_buttons_arr); + +/* Fixed report descriptor for (tweaked) v2 frame touch ring reports */ +const __u8 uclogic_rdesc_v2_frame_touch_ring_arr[] = { + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x07, /* Usage (Keypad), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, UCLOGIC_RDESC_V2_FRAME_TOUCH_ID, + /* Report ID (TOUCH_ID), */ + 0x14, /* Logical Minimum (0), */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x39, /* Usage (Tablet Function Keys), */ + 0xA0, /* Collection (Physical), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x05, 0x09, /* Usage Page (Button), */ + 0x09, 0x01, /* Usage (01h), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x07, /* Report Count (7), */ + 0x81, 0x01, /* Input (Constant), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x02, /* Report Count (2), */ + 0x81, 0x01, /* Input (Constant), */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x0A, 0xFF, 0xFF, /* Usage (FFFFh), */ + 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x38, /* Usage (Wheel), */ + 0x95, 0x01, /* Report Count (1), */ + 0x15, 0x00, /* Logical Minimum (0), */ + 0x25, 0x0B, /* Logical Maximum (11), */ + 0x81, 0x02, /* Input (Variable), */ + 0x09, 0x30, /* Usage (X), */ + 0x09, 0x31, /* Usage (Y), */ + 0x14, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x02, /* Report Count (2), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x2E, /* Report Count (46), */ + 0x81, 0x01, /* Input (Constant), */ + 0xC0, /* End Collection, */ + 0xC0 /* End Collection */ +}; +const size_t uclogic_rdesc_v2_frame_touch_ring_size = + sizeof(uclogic_rdesc_v2_frame_touch_ring_arr); + +/* Fixed report descriptor for (tweaked) v2 frame touch strip reports */ +const __u8 uclogic_rdesc_v2_frame_touch_strip_arr[] = { + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x07, /* Usage (Keypad), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, UCLOGIC_RDESC_V2_FRAME_TOUCH_ID, + /* Report ID (TOUCH_ID), */ + 0x14, /* Logical Minimum (0), */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x39, /* Usage (Tablet Function Keys), */ + 0xA0, /* Collection (Physical), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x05, 0x09, /* Usage Page (Button), */ + 0x09, 0x01, /* Usage (01h), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x07, /* Report Count (7), */ + 0x81, 0x01, /* Input (Constant), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x02, /* Report Count (2), */ + 0x81, 0x01, /* Input (Constant), */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x0A, 0xFF, 0xFF, /* Usage (FFFFh), */ + 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x38, /* Usage (Wheel), */ + 0x95, 0x01, /* Report Count (1), */ + 0x15, 0x00, /* Logical Minimum (0), */ + 0x25, 0x07, /* Logical Maximum (7), */ + 0x81, 0x02, /* Input (Variable), */ + 0x09, 0x30, /* Usage (X), */ + 0x09, 0x31, /* Usage (Y), */ + 0x14, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x02, /* Report Count (2), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x2E, /* Report Count (46), */ + 0x81, 0x01, /* Input (Constant), */ + 0xC0, /* End Collection, */ + 0xC0 /* End Collection */ +}; +const size_t uclogic_rdesc_v2_frame_touch_strip_size = + sizeof(uclogic_rdesc_v2_frame_touch_strip_arr); + +/* Fixed report descriptor for (tweaked) v2 frame dial reports */ +const __u8 uclogic_rdesc_v2_frame_dial_arr[] = { + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x07, /* Usage (Keypad), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, UCLOGIC_RDESC_V2_FRAME_DIAL_ID, + /* Report ID (DIAL_ID), */ + 0x14, /* Logical Minimum (0), */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x39, /* Usage (Tablet Function Keys), */ + 0xA0, /* Collection (Physical), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x01, /* Input (Constant), */ + 0x05, 0x09, /* Usage Page (Button), */ + 0x09, 0x01, /* Usage (01h), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x06, /* Report Count (6), */ + 0x81, 0x01, /* Input (Constant), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x02, /* Report Count (2), */ + 0x81, 0x01, /* Input (Constant), */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x0A, 0xFF, 0xFF, /* Usage (FFFFh), */ + 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x38, /* Usage (Wheel), */ + 0x95, 0x01, /* Report Count (1), */ + 0x15, 0xFF, /* Logical Minimum (-1), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x81, 0x06, /* Input (Variable, Relative), */ + 0x09, 0x30, /* Usage (X), */ + 0x09, 0x31, /* Usage (Y), */ + 0x14, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x02, /* Report Count (2), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x2E, /* Report Count (46), */ + 0x81, 0x01, /* Input (Constant), */ + 0xC0, /* End Collection, */ + 0xC0 /* End Collection */ +}; +const size_t uclogic_rdesc_v2_frame_dial_size = + sizeof(uclogic_rdesc_v2_frame_dial_arr); /* Fixed report descriptor for Ugee EX07 frame */ const __u8 uclogic_rdesc_ugee_ex07_frame_arr[] = { diff --git a/drivers/hid/hid-uclogic-rdesc.h b/drivers/hid/hid-uclogic-rdesc.h index 3d904c27b86a..0c6e95e8bde7 100644 --- a/drivers/hid/hid-uclogic-rdesc.h +++ b/drivers/hid/hid-uclogic-rdesc.h @@ -124,12 +124,36 @@ extern const size_t uclogic_rdesc_v2_pen_template_size; extern const __u8 uclogic_rdesc_v1_frame_arr[]; extern const size_t uclogic_rdesc_v1_frame_size; -/* Report ID for tweaked v2 frame reports */ -#define UCLOGIC_RDESC_V2_FRAME_ID 0xf7 +/* Report ID for tweaked v2 frame button reports */ +#define UCLOGIC_RDESC_V2_FRAME_BUTTONS_ID 0xf7 -/* Fixed report descriptor for (tweaked) v2 frame reports */ -extern const __u8 uclogic_rdesc_v2_frame_arr[]; -extern const size_t uclogic_rdesc_v2_frame_size; +/* Fixed report descriptor for (tweaked) v2 frame button reports */ +extern const __u8 uclogic_rdesc_v2_frame_buttons_arr[]; +extern const size_t uclogic_rdesc_v2_frame_buttons_size; + +/* Report ID for tweaked v2 frame touch ring/strip reports */ +#define UCLOGIC_RDESC_V2_FRAME_TOUCH_ID 0xf8 + +/* Fixed report descriptor for (tweaked) v2 frame touch ring reports */ +extern const __u8 uclogic_rdesc_v2_frame_touch_ring_arr[]; +extern const size_t uclogic_rdesc_v2_frame_touch_ring_size; + +/* Fixed report descriptor for (tweaked) v2 frame touch strip reports */ +extern const __u8 uclogic_rdesc_v2_frame_touch_strip_arr[]; +extern const size_t uclogic_rdesc_v2_frame_touch_strip_size; + +/* Device ID byte offset in v2 frame touch ring/strip reports */ +#define UCLOGIC_RDESC_V2_FRAME_TOUCH_DEV_ID_BYTE 0x4 + +/* Report ID for tweaked v2 frame dial reports */ +#define UCLOGIC_RDESC_V2_FRAME_DIAL_ID 0xf9 + +/* Fixed report descriptor for (tweaked) v2 frame dial reports */ +extern const __u8 uclogic_rdesc_v2_frame_dial_arr[]; +extern const size_t uclogic_rdesc_v2_frame_dial_size; + +/* Device ID byte offset in v2 frame dial reports */ +#define UCLOGIC_RDESC_V2_FRAME_DIAL_DEV_ID_BYTE 0x4 /* Fixed report descriptor for Ugee EX07 frame */ extern const __u8 uclogic_rdesc_ugee_ex07_frame_arr[]; diff --git a/drivers/hid/hid-viewsonic.c b/drivers/hid/hid-viewsonic.c index df60c8fc2efd..8024b1d370e2 100644 --- a/drivers/hid/hid-viewsonic.c +++ b/drivers/hid/hid-viewsonic.c @@ -24,7 +24,7 @@ /* Fixed report descriptor of PD1011 signature pad */ static __u8 pd1011_rdesc_fixed[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ - 0x09, 0x02, /* Usage (Pen), */ + 0x09, 0x01, /* Usage (Digitizer), */ 0xA1, 0x01, /* Collection (Application), */ 0x85, 0x02, /* Report ID (2), */ 0x09, 0x20, /* Usage (Stylus), */ diff --git a/drivers/hid/intel-ish-hid/ipc/hw-ish.h b/drivers/hid/intel-ish-hid/ipc/hw-ish.h index 07e3cbc86bef..e600dbf04dfc 100644 --- a/drivers/hid/intel-ish-hid/ipc/hw-ish.h +++ b/drivers/hid/intel-ish-hid/ipc/hw-ish.h @@ -30,6 +30,8 @@ #define TGL_H_DEVICE_ID 0x43FC #define ADL_S_DEVICE_ID 0x7AF8 #define ADL_P_DEVICE_ID 0x51FC +#define ADL_N_DEVICE_ID 0x54FC +#define RPL_S_DEVICE_ID 0x7A78 #define REVISION_ID_CHT_A0 0x6 #define REVISION_ID_CHT_Ax_SI 0x0 diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c index 8e9d9450cb83..2c67ec17bec6 100644 --- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c +++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c @@ -41,6 +41,8 @@ static const struct pci_device_id ish_pci_tbl[] = { {PCI_DEVICE(PCI_VENDOR_ID_INTEL, TGL_H_DEVICE_ID)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_S_DEVICE_ID)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_P_DEVICE_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_N_DEVICE_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, RPL_S_DEVICE_ID)}, {0, } }; MODULE_DEVICE_TABLE(pci, ish_pci_tbl); diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 066c567dbaa2..620fe74f5676 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1777,7 +1777,7 @@ static int __wacom_initialize_battery(struct wacom *wacom, bat_desc->get_property = wacom_battery_get_property; sprintf(battery->bat_name, "wacom_battery_%ld", n); bat_desc->name = battery->bat_name; - bat_desc->type = POWER_SUPPLY_TYPE_USB; + bat_desc->type = POWER_SUPPLY_TYPE_BATTERY; bat_desc->use_for_apm = 0; ps_bat = devm_power_supply_register(dev, bat_desc, &psy_cfg); diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index a7176fc0635d..9470c2b0b529 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -1811,7 +1811,9 @@ int wacom_equivalent_usage(int usage) usage == WACOM_HID_WD_TOUCHSTRIP2 || usage == WACOM_HID_WD_TOUCHRING || usage == WACOM_HID_WD_TOUCHRINGSTATUS || - usage == WACOM_HID_WD_REPORT_VALID) { + usage == WACOM_HID_WD_REPORT_VALID || + usage == WACOM_HID_WD_BARRELSWITCH3 || + usage == WACOM_HID_WD_SEQUENCENUMBER) { return usage; } @@ -2196,8 +2198,11 @@ static void wacom_set_barrel_switch3_usage(struct wacom_wac *wacom_wac) if (!(features->quirks & WACOM_QUIRK_AESPEN) && wacom_wac->hid_data.barrelswitch && wacom_wac->hid_data.barrelswitch2 && - wacom_wac->hid_data.serialhi) + wacom_wac->hid_data.serialhi && + !wacom_wac->hid_data.barrelswitch3) { input_set_capability(input, EV_KEY, BTN_STYLUS3); + features->quirks |= WACOM_QUIRK_PEN_BUTTON3; + } } static void wacom_wac_pen_usage_mapping(struct hid_device *hdev, @@ -2261,6 +2266,9 @@ static void wacom_wac_pen_usage_mapping(struct hid_device *hdev, features->quirks |= WACOM_QUIRK_TOOLSERIAL; wacom_map_usage(input, usage, field, EV_MSC, MSC_SERIAL, 0); break; + case HID_DG_SCANTIME: + wacom_map_usage(input, usage, field, EV_MSC, MSC_TIMESTAMP, 0); + break; case WACOM_HID_WD_SENSE: features->quirks |= WACOM_QUIRK_SENSE; wacom_map_usage(input, usage, field, EV_KEY, BTN_TOOL_PEN, 0); @@ -2274,6 +2282,11 @@ static void wacom_wac_pen_usage_mapping(struct hid_device *hdev, input_set_capability(input, EV_KEY, BTN_TOOL_AIRBRUSH); wacom_map_usage(input, usage, field, EV_ABS, ABS_WHEEL, 0); break; + case WACOM_HID_WD_BARRELSWITCH3: + wacom_wac->hid_data.barrelswitch3 = true; + wacom_map_usage(input, usage, field, EV_KEY, BTN_STYLUS3, 0); + features->quirks &= ~WACOM_QUIRK_PEN_BUTTON3; + break; } } @@ -2390,6 +2403,14 @@ static void wacom_wac_pen_event(struct hid_device *hdev, struct hid_field *field case WACOM_HID_WD_REPORT_VALID: wacom_wac->is_invalid_bt_frame = !value; return; + case WACOM_HID_WD_BARRELSWITCH3: + wacom_wac->hid_data.barrelswitch3 = value; + return; + case WACOM_HID_WD_SEQUENCENUMBER: + if (wacom_wac->hid_data.sequence_number != value) + hid_warn(hdev, "Dropped %hu packets", (unsigned short)(value - wacom_wac->hid_data.sequence_number)); + wacom_wac->hid_data.sequence_number = value + 1; + return; } /* send pen events only when touch is up or forced out @@ -2442,12 +2463,15 @@ static void wacom_wac_pen_report(struct hid_device *hdev, if (!delay_pen_events(wacom_wac) && wacom_wac->tool[0]) { int id = wacom_wac->id[0]; - int sw_state = wacom_wac->hid_data.barrelswitch | - (wacom_wac->hid_data.barrelswitch2 << 1); - - input_report_key(input, BTN_STYLUS, sw_state == 1); - input_report_key(input, BTN_STYLUS2, sw_state == 2); - input_report_key(input, BTN_STYLUS3, sw_state == 3); + if (wacom_wac->features.quirks & WACOM_QUIRK_PEN_BUTTON3 && + wacom_wac->hid_data.barrelswitch & wacom_wac->hid_data.barrelswitch2) { + wacom_wac->hid_data.barrelswitch = 0; + wacom_wac->hid_data.barrelswitch2 = 0; + wacom_wac->hid_data.barrelswitch3 = 1; + } + input_report_key(input, BTN_STYLUS, wacom_wac->hid_data.barrelswitch); + input_report_key(input, BTN_STYLUS2, wacom_wac->hid_data.barrelswitch2); + input_report_key(input, BTN_STYLUS3, wacom_wac->hid_data.barrelswitch3); /* * Non-USI EMR tools should have their IDs mangled to @@ -2529,6 +2553,9 @@ static void wacom_wac_finger_usage_mapping(struct hid_device *hdev, field->logical_maximum = 255; } break; + case HID_DG_SCANTIME: + wacom_map_usage(input, usage, field, EV_MSC, MSC_TIMESTAMP, 0); + break; } } diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index 466b62cc16dc..fef1538005b5 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -86,6 +86,7 @@ #define WACOM_QUIRK_AESPEN 0x0004 #define WACOM_QUIRK_BATTERY 0x0008 #define WACOM_QUIRK_TOOLSERIAL 0x0010 +#define WACOM_QUIRK_PEN_BUTTON3 0x0020 /* device types */ #define WACOM_DEVICETYPE_NONE 0x0000 @@ -108,6 +109,7 @@ #define WACOM_HID_WD_DIGITIZERFNKEYS (WACOM_HID_UP_WACOMDIGITIZER | 0x39) #define WACOM_HID_WD_SERIALNUMBER (WACOM_HID_UP_WACOMDIGITIZER | 0x5b) #define WACOM_HID_WD_SERIALHI (WACOM_HID_UP_WACOMDIGITIZER | 0x5c) +#define WACOM_HID_WD_BARRELSWITCH3 (WACOM_HID_UP_WACOMDIGITIZER | 0x5d) #define WACOM_HID_WD_TOOLTYPE (WACOM_HID_UP_WACOMDIGITIZER | 0x77) #define WACOM_HID_WD_DISTANCE (WACOM_HID_UP_WACOMDIGITIZER | 0x0132) #define WACOM_HID_WD_TOUCHSTRIP (WACOM_HID_UP_WACOMDIGITIZER | 0x0136) @@ -115,6 +117,7 @@ #define WACOM_HID_WD_TOUCHRING (WACOM_HID_UP_WACOMDIGITIZER | 0x0138) #define WACOM_HID_WD_TOUCHRINGSTATUS (WACOM_HID_UP_WACOMDIGITIZER | 0x0139) #define WACOM_HID_WD_REPORT_VALID (WACOM_HID_UP_WACOMDIGITIZER | 0x01d0) +#define WACOM_HID_WD_SEQUENCENUMBER (WACOM_HID_UP_WACOMDIGITIZER | 0x0220) #define WACOM_HID_WD_ACCELEROMETER_X (WACOM_HID_UP_WACOMDIGITIZER | 0x0401) #define WACOM_HID_WD_ACCELEROMETER_Y (WACOM_HID_UP_WACOMDIGITIZER | 0x0402) #define WACOM_HID_WD_ACCELEROMETER_Z (WACOM_HID_UP_WACOMDIGITIZER | 0x0403) @@ -300,6 +303,7 @@ struct hid_data { bool tipswitch; bool barrelswitch; bool barrelswitch2; + bool barrelswitch3; bool serialhi; bool confidence; int x; @@ -320,6 +324,7 @@ struct hid_data { int bat_connected; int ps_connected; bool pad_input_event_flag; + unsigned short sequence_number; }; struct wacom_remote_data { diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index f2b038fa3b84..590d3d550acb 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -256,11 +256,14 @@ config SENSORS_AHT10 will be called aht10. config SENSORS_AQUACOMPUTER_D5NEXT - tristate "Aquacomputer D5 Next watercooling pump" + tristate "Aquacomputer D5 Next, Octo, Farbwerk, and Farbwerk 360" depends on USB_HID + select CRC16 help - If you say yes here you get support for the Aquacomputer D5 Next - watercooling pump sensors. + If you say yes here you get support for sensors and fans of + the Aquacomputer D5 Next watercooling pump, Octo fan + controller, Farbwerk and Farbwerk 360 RGB controllers, where + available. This driver can also be built as a module. If so, the module will be called aquacomputer_d5next. @@ -415,6 +418,7 @@ config SENSORS_ATXP1 config SENSORS_BT1_PVT tristate "Baikal-T1 Process, Voltage, Temperature sensor driver" depends on MIPS_BAIKAL_T1 || COMPILE_TEST + select POLYNOMIAL help If you say yes here you get support for Baikal-T1 PVT sensor embedded into the SoC. @@ -498,6 +502,7 @@ config SENSORS_DS1621 config SENSORS_DELL_SMM tristate "Dell laptop SMM BIOS hwmon driver" depends on X86 + imply THERMAL help This hwmon driver adds support for reporting temperature of different sensors and controls the fans on Dell laptops via System Management @@ -814,6 +819,18 @@ config SENSORS_POWR1220 This driver can also be built as a module. If so, the module will be called powr1220. +config SENSORS_LAN966X + tristate "Microchip LAN966x Hardware Monitoring" + depends on SOC_LAN966 || COMPILE_TEST + select REGMAP + select POLYNOMIAL + help + If you say yes here you get support for temperature monitoring + on the Microchip LAN966x SoC. + + This driver can also be built as a module. If so, the module + will be called lan966x-hwmon. + config SENSORS_LINEAGE tristate "Lineage Compact Power Line Power Entry Module" depends on I2C @@ -1248,6 +1265,7 @@ config SENSORS_LM75 temperature sensor chip, with models including: - Analog Devices ADT75 + - Atmel (now Microchip) AT30TS74 - Dallas Semiconductor DS75, DS1775 and DS7505 - Global Mixed-mode Technology (GMT) G751 - Maxim MAX6625 and MAX6626 @@ -1457,11 +1475,23 @@ config SENSORS_NCT6683 This driver can also be built as a module. If so, the module will be called nct6683. +config SENSORS_NCT6775_CORE + tristate + select REGMAP + help + This module contains common code shared by the platform and + i2c versions of the nct6775 driver; it is not useful on its + own. + + If built as a module, the module will be called + nct6775-core. + config SENSORS_NCT6775 - tristate "Nuvoton NCT6775F and compatibles" + tristate "Platform driver for Nuvoton NCT6775F and compatibles" depends on !PPC depends on ACPI_WMI || ACPI_WMI=n select HWMON_VID + select SENSORS_NCT6775_CORE help If you say yes here you get support for the hardware monitoring functionality of the Nuvoton NCT6106D, NCT6775F, NCT6776F, NCT6779D, @@ -1472,6 +1502,23 @@ config SENSORS_NCT6775 This driver can also be built as a module. If so, the module will be called nct6775. +config SENSORS_NCT6775_I2C + tristate "I2C driver for Nuvoton NCT6775F and compatibles" + depends on I2C + select REGMAP_I2C + select SENSORS_NCT6775_CORE + help + If you say yes here you get support for the hardware monitoring + functionality of the Nuvoton NCT6106D, NCT6775F, NCT6776F, NCT6779D, + NCT6791D, NCT6792D, NCT6793D, NCT6795D, NCT6796D, and compatible + Super-I/O chips via their I2C interface. + + If you're not building a kernel for a BMC, this is probably + not the driver you want (see CONFIG_SENSORS_NCT6775). + + This driver can also be built as a module. If so, the module + will be called nct6775-i2c. + config SENSORS_NCT7802 tristate "Nuvoton NCT7802Y" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 8a03289e2aa4..007e829d1d0d 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -100,6 +100,7 @@ obj-$(CONFIG_SENSORS_IT87) += it87.o obj-$(CONFIG_SENSORS_JC42) += jc42.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o +obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o obj-$(CONFIG_SENSORS_LM63) += lm63.o @@ -154,7 +155,10 @@ obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o obj-$(CONFIG_SENSORS_MR75203) += mr75203.o obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o +obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o +nct6775-objs := nct6775-platform.o obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o +obj-$(CONFIG_SENSORS_NCT6775_I2C) += nct6775-i2c.o obj-$(CONFIG_SENSORS_NCT7802) += nct7802.o obj-$(CONFIG_SENSORS_NCT7904) += nct7904.o obj-$(CONFIG_SENSORS_NPCM7XX) += npcm750-pwm-fan.o diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index c405a5869581..d2545a1be9fc 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -481,7 +481,7 @@ static struct sensor_template meter_attrs[] = { RO_SENSOR_TEMPLATE("power1_average_interval_max", show_val, 1), RO_SENSOR_TEMPLATE("power1_is_battery", show_val, 5), RW_SENSOR_TEMPLATE(POWER_AVG_INTERVAL_NAME, show_avg_interval, - set_avg_interval, 0), + set_avg_interval, 0), {}, }; @@ -530,6 +530,7 @@ static void remove_domain_devices(struct acpi_power_meter_resource *resource) for (i = 0; i < resource->num_domain_devices; i++) { struct acpi_device *obj = resource->domain_devices[i]; + if (!obj) continue; @@ -580,7 +581,7 @@ static int read_domain_devices(struct acpi_power_meter_resource *resource) } resource->holders_dir = kobject_create_and_add("measures", - &resource->acpi_dev->dev.kobj); + &resource->acpi_dev->dev.kobj); if (!resource->holders_dir) { res = -ENOMEM; goto exit_free; @@ -590,7 +591,7 @@ static int read_domain_devices(struct acpi_power_meter_resource *resource) for (i = 0; i < pss->package.count; i++) { struct acpi_device *obj; - union acpi_object *element = &(pss->package.elements[i]); + union acpi_object *element = &pss->package.elements[i]; /* Refuse non-references */ if (element->type != ACPI_TYPE_LOCAL_REFERENCE) @@ -603,7 +604,7 @@ static int read_domain_devices(struct acpi_power_meter_resource *resource) continue; res = sysfs_create_link(resource->holders_dir, &obj->dev.kobj, - kobject_name(&obj->dev.kobj)); + kobject_name(&obj->dev.kobj)); if (res) { acpi_dev_put(obj); resource->domain_devices[i] = NULL; @@ -788,7 +789,7 @@ static int read_capabilities(struct acpi_power_meter_resource *resource) str = &resource->model_number; for (i = 11; i < 14; i++) { - union acpi_object *element = &(pss->package.elements[i]); + union acpi_object *element = &pss->package.elements[i]; if (element->type != ACPI_TYPE_STRING) { res = -EINVAL; @@ -868,8 +869,7 @@ static int acpi_power_meter_add(struct acpi_device *device) if (!device) return -EINVAL; - resource = kzalloc(sizeof(struct acpi_power_meter_resource), - GFP_KERNEL); + resource = kzalloc(sizeof(*resource), GFP_KERNEL); if (!resource) return -ENOMEM; @@ -884,7 +884,8 @@ static int acpi_power_meter_add(struct acpi_device *device) if (res) goto exit_free; - resource->trip[0] = resource->trip[1] = -1; + resource->trip[0] = -1; + resource->trip[1] = -1; res = setup_attrs(resource); if (res) diff --git a/drivers/hwmon/adt7475.c b/drivers/hwmon/adt7475.c index 9d5b019651f2..ac480e6e4818 100644 --- a/drivers/hwmon/adt7475.c +++ b/drivers/hwmon/adt7475.c @@ -112,6 +112,8 @@ #define CONFIG3_THERM 0x02 #define CONFIG4_PINFUNC 0x03 +#define CONFIG4_THERM 0x01 +#define CONFIG4_SMBALERT 0x02 #define CONFIG4_MAXDUTY 0x08 #define CONFIG4_ATTN_IN10 0x30 #define CONFIG4_ATTN_IN43 0xC0 @@ -1460,6 +1462,96 @@ static int adt7475_update_limits(struct i2c_client *client) return 0; } +static int load_config3(const struct i2c_client *client, const char *propname) +{ + const char *function; + u8 config3; + int ret; + + ret = of_property_read_string(client->dev.of_node, propname, &function); + if (!ret) { + ret = adt7475_read(REG_CONFIG3); + if (ret < 0) + return ret; + + config3 = ret & ~CONFIG3_SMBALERT; + if (!strcmp("pwm2", function)) + ; + else if (!strcmp("smbalert#", function)) + config3 |= CONFIG3_SMBALERT; + else + return -EINVAL; + + return i2c_smbus_write_byte_data(client, REG_CONFIG3, config3); + } + + return 0; +} + +static int load_config4(const struct i2c_client *client, const char *propname) +{ + const char *function; + u8 config4; + int ret; + + ret = of_property_read_string(client->dev.of_node, propname, &function); + if (!ret) { + ret = adt7475_read(REG_CONFIG4); + if (ret < 0) + return ret; + + config4 = ret & ~CONFIG4_PINFUNC; + + if (!strcmp("tach4", function)) + ; + else if (!strcmp("therm#", function)) + config4 |= CONFIG4_THERM; + else if (!strcmp("smbalert#", function)) + config4 |= CONFIG4_SMBALERT; + else if (!strcmp("gpio", function)) + config4 |= CONFIG4_PINFUNC; + else + return -EINVAL; + + return i2c_smbus_write_byte_data(client, REG_CONFIG4, config4); + } + + return 0; +} + +static int load_config(const struct i2c_client *client, enum chips chip) +{ + int err; + const char *prop1, *prop2; + + switch (chip) { + case adt7473: + case adt7475: + prop1 = "adi,pin5-function"; + prop2 = "adi,pin9-function"; + break; + case adt7476: + case adt7490: + prop1 = "adi,pin10-function"; + prop2 = "adi,pin14-function"; + break; + } + + err = load_config3(client, prop1); + if (err) { + dev_err(&client->dev, "failed to configure %s\n", prop1); + return err; + } + + err = load_config4(client, prop2); + if (err) { + dev_err(&client->dev, "failed to configure %s\n", prop2); + return err; + } + + return 0; +} + static int set_property_bit(const struct i2c_client *client, char *property, u8 *config, u8 bit_index) { @@ -1477,12 +1569,12 @@ static int set_property_bit(const struct i2c_client *client, char *property, return ret; } -static int load_attenuators(const struct i2c_client *client, int chip, +static int load_attenuators(const struct i2c_client *client, enum chips chip, struct adt7475_data *data) { - int ret; - - if (chip == adt7476 || chip == adt7490) { + switch (chip) { + case adt7476: + case adt7490: set_property_bit(client, "adi,bypass-attenuator-in0", &data->config4, 4); set_property_bit(client, "adi,bypass-attenuator-in1", @@ -1492,18 +1584,15 @@ static int load_attenuators(const struct i2c_client *client, int chip, set_property_bit(client, "adi,bypass-attenuator-in4", &data->config4, 7); - ret = i2c_smbus_write_byte_data(client, REG_CONFIG4, - data->config4); - if (ret < 0) - return ret; - } else if (chip == adt7473 || chip == adt7475) { + return i2c_smbus_write_byte_data(client, REG_CONFIG4, + data->config4); + case adt7473: + case adt7475: set_property_bit(client, "adi,bypass-attenuator-in1", &data->config2, 5); - ret = i2c_smbus_write_byte_data(client, REG_CONFIG2, - data->config2); - if (ret < 0) - return ret; + return i2c_smbus_write_byte_data(client, REG_CONFIG2, + data->config2); } return 0; @@ -1585,6 +1674,10 @@ static int adt7475_probe(struct i2c_client *client) revision = adt7475_read(REG_DEVID2) & 0x07; } + ret = load_config(client, chip); + if (ret) + return ret; + config3 = adt7475_read(REG_CONFIG3); /* Pin PWM2 may alternatively be used for ALERT output */ if (!(config3 & CONFIG3_SMBALERT)) diff --git a/drivers/hwmon/aquacomputer_d5next.c b/drivers/hwmon/aquacomputer_d5next.c index 525809cf7c95..a0e69f7ece36 100644 --- a/drivers/hwmon/aquacomputer_d5next.c +++ b/drivers/hwmon/aquacomputer_d5next.c @@ -1,30 +1,37 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * hwmon driver for Aquacomputer devices (D5 Next, Farbwerk 360) + * hwmon driver for Aquacomputer devices (D5 Next, Farbwerk, Farbwerk 360, Octo) * * Aquacomputer devices send HID reports (with ID 0x01) every second to report * sensor values. * * Copyright 2021 Aleksa Savic + * Copyright 2022 Jack Doan */ +#include #include #include #include #include #include +#include #include #include #define USB_VENDOR_ID_AQUACOMPUTER 0x0c70 +#define USB_PRODUCT_ID_FARBWERK 0xf00a #define USB_PRODUCT_ID_D5NEXT 0xf00e #define USB_PRODUCT_ID_FARBWERK360 0xf010 +#define USB_PRODUCT_ID_OCTO 0xf011 -enum kinds { d5next, farbwerk360 }; +enum kinds { d5next, farbwerk, farbwerk360, octo }; static const char *const aqc_device_names[] = { [d5next] = "d5next", - [farbwerk360] = "farbwerk360" + [farbwerk] = "farbwerk", + [farbwerk360] = "farbwerk360", + [octo] = "octo" }; #define DRIVER_NAME "aquacomputer_d5next" @@ -35,6 +42,18 @@ static const char *const aqc_device_names[] = { #define SERIAL_SECOND_PART 5 #define FIRMWARE_VERSION 13 +#define CTRL_REPORT_ID 0x03 + +/* The HID report that the official software always sends + * after writing values, currently same for all devices + */ +#define SECONDARY_CTRL_REPORT_ID 0x02 +#define SECONDARY_CTRL_REPORT_SIZE 0x0B + +static u8 secondary_ctrl_report[] = { + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x34, 0xC6 +}; + /* Register offsets for the D5 Next pump */ #define D5NEXT_POWER_CYCLES 24 @@ -53,14 +72,46 @@ static const char *const aqc_device_names[] = { #define D5NEXT_PUMP_CURRENT 112 #define D5NEXT_FAN_CURRENT 99 +/* Register offsets for the Farbwerk RGB controller */ +#define FARBWERK_NUM_SENSORS 4 +#define FARBWERK_SENSOR_START 0x2f +#define FARBWERK_SENSOR_SIZE 0x02 +#define FARBWERK_SENSOR_DISCONNECTED 0x7FFF + /* Register offsets for the Farbwerk 360 RGB controller */ #define FARBWERK360_NUM_SENSORS 4 -#define FARBWERK360_SENSOR_START 0x32 +#define FARBWERK360_SENSOR_START 0x32 #define FARBWERK360_SENSOR_SIZE 0x02 #define FARBWERK360_SENSOR_DISCONNECTED 0x7FFF +/* Register offsets for the Octo fan controller */ +#define OCTO_POWER_CYCLES 0x18 +#define OCTO_NUM_FANS 8 +#define OCTO_FAN_PERCENT_OFFSET 0x00 +#define OCTO_FAN_VOLTAGE_OFFSET 0x02 +#define OCTO_FAN_CURRENT_OFFSET 0x04 +#define OCTO_FAN_POWER_OFFSET 0x06 +#define OCTO_FAN_SPEED_OFFSET 0x08 + +static u8 octo_sensor_fan_offsets[] = { 0x7D, 0x8A, 0x97, 0xA4, 0xB1, 0xBE, 0xCB, 0xD8 }; + +#define OCTO_NUM_SENSORS 4 +#define OCTO_SENSOR_START 0x3D +#define OCTO_SENSOR_SIZE 0x02 +#define OCTO_SENSOR_DISCONNECTED 0x7FFF + +#define OCTO_CTRL_REPORT_SIZE 0x65F +#define OCTO_CTRL_REPORT_CHECKSUM_OFFSET 0x65D +#define OCTO_CTRL_REPORT_CHECKSUM_START 0x01 +#define OCTO_CTRL_REPORT_CHECKSUM_LENGTH 0x65C + +/* Fan speed registers in Octo control report (from 0-100%) */ +static u16 octo_ctrl_fan_offsets[] = { 0x5B, 0xB0, 0x105, 0x15A, 0x1AF, 0x204, 0x259, 0x2AE }; + /* Labels for D5 Next */ -#define L_D5NEXT_COOLANT_TEMP "Coolant temp" +static const char *const label_d5next_temp[] = { + "Coolant temp" +}; static const char *const label_d5next_speeds[] = { "Pump speed", @@ -83,7 +134,7 @@ static const char *const label_d5next_current[] = { "Fan current" }; -/* Labels for Farbwerk 360 temperature sensors */ +/* Labels for Farbwerk, Farbwerk 360 and Octo temperature sensors */ static const char *const label_temp_sensors[] = { "Sensor 1", "Sensor 2", @@ -91,32 +142,182 @@ static const char *const label_temp_sensors[] = { "Sensor 4" }; +/* Labels for Octo */ +static const char *const label_fan_speed[] = { + "Fan 1 speed", + "Fan 2 speed", + "Fan 3 speed", + "Fan 4 speed", + "Fan 5 speed", + "Fan 6 speed", + "Fan 7 speed", + "Fan 8 speed" +}; + +static const char *const label_fan_power[] = { + "Fan 1 power", + "Fan 2 power", + "Fan 3 power", + "Fan 4 power", + "Fan 5 power", + "Fan 6 power", + "Fan 7 power", + "Fan 8 power" +}; + +static const char *const label_fan_voltage[] = { + "Fan 1 voltage", + "Fan 2 voltage", + "Fan 3 voltage", + "Fan 4 voltage", + "Fan 5 voltage", + "Fan 6 voltage", + "Fan 7 voltage", + "Fan 8 voltage" +}; + +static const char *const label_fan_current[] = { + "Fan 1 current", + "Fan 2 current", + "Fan 3 current", + "Fan 4 current", + "Fan 5 current", + "Fan 6 current", + "Fan 7 current", + "Fan 8 current" +}; + struct aqc_data { struct hid_device *hdev; struct device *hwmon_dev; struct dentry *debugfs; + struct mutex mutex; /* Used for locking access when reading and writing PWM values */ enum kinds kind; const char *name; + int buffer_size; + u8 *buffer; + int checksum_start; + int checksum_length; + int checksum_offset; + /* General info, same across all devices */ u32 serial_number[2]; u16 firmware_version; - /* D5 Next specific - how many times the device was powered on */ + /* How many times the device was powered on */ u32 power_cycles; /* Sensor values */ s32 temp_input[4]; - u16 speed_input[2]; - u32 power_input[2]; - u16 voltage_input[3]; - u16 current_input[2]; + u16 speed_input[8]; + u32 power_input[8]; + u16 voltage_input[8]; + u16 current_input[8]; + + /* Label values */ + const char *const *temp_label; + const char *const *speed_label; + const char *const *power_label; + const char *const *voltage_label; + const char *const *current_label; unsigned long updated; }; -static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, - int channel) +/* Converts from centi-percent */ +static int aqc_percent_to_pwm(u16 val) +{ + return DIV_ROUND_CLOSEST(val * 255, 100 * 100); +} + +/* Converts to centi-percent */ +static int aqc_pwm_to_percent(long val) +{ + if (val < 0 || val > 255) + return -EINVAL; + + return DIV_ROUND_CLOSEST(val * 100 * 100, 255); +} + +/* Expects the mutex to be locked */ +static int aqc_get_ctrl_data(struct aqc_data *priv) +{ + int ret; + + memset(priv->buffer, 0x00, priv->buffer_size); + ret = hid_hw_raw_request(priv->hdev, CTRL_REPORT_ID, priv->buffer, priv->buffer_size, + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + if (ret < 0) + ret = -ENODATA; + + return ret; +} + +/* Expects the mutex to be locked */ +static int aqc_send_ctrl_data(struct aqc_data *priv) +{ + int ret; + u16 checksum; + + /* Init and xorout value for CRC-16/USB is 0xffff */ + checksum = crc16(0xffff, priv->buffer + priv->checksum_start, priv->checksum_length); + checksum ^= 0xffff; + + /* Place the new checksum at the end of the report */ + put_unaligned_be16(checksum, priv->buffer + priv->checksum_offset); + + /* Send the patched up report back to the device */ + ret = hid_hw_raw_request(priv->hdev, CTRL_REPORT_ID, priv->buffer, priv->buffer_size, + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + if (ret < 0) + return ret; + + /* The official software sends this report after every change, so do it here as well */ + ret = hid_hw_raw_request(priv->hdev, SECONDARY_CTRL_REPORT_ID, secondary_ctrl_report, + SECONDARY_CTRL_REPORT_SIZE, HID_FEATURE_REPORT, + HID_REQ_SET_REPORT); + return ret; +} + +/* Refreshes the control buffer and returns value at offset */ +static int aqc_get_ctrl_val(struct aqc_data *priv, int offset) +{ + int ret; + + mutex_lock(&priv->mutex); + + ret = aqc_get_ctrl_data(priv); + if (ret < 0) + goto unlock_and_return; + + ret = get_unaligned_be16(priv->buffer + offset); + +unlock_and_return: + mutex_unlock(&priv->mutex); + return ret; +} + +static int aqc_set_ctrl_val(struct aqc_data *priv, int offset, long val) +{ + int ret; + + mutex_lock(&priv->mutex); + + ret = aqc_get_ctrl_data(priv); + if (ret < 0) + goto unlock_and_return; + + put_unaligned_be16((u16)val, priv->buffer + offset); + + ret = aqc_send_ctrl_data(priv); + +unlock_and_return: + mutex_unlock(&priv->mutex); + return ret; +} + +static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { const struct aqc_data *priv = data; @@ -127,18 +328,49 @@ static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u3 if (channel == 0) return 0444; break; + case farbwerk: case farbwerk360: + case octo: return 0444; default: break; } break; + case hwmon_pwm: + switch (priv->kind) { + case octo: + switch (attr) { + case hwmon_pwm_input: + return 0644; + default: + break; + } + break; + default: + break; + } + break; case hwmon_fan: case hwmon_power: - case hwmon_in: case hwmon_curr: switch (priv->kind) { case d5next: + if (channel < 2) + return 0444; + break; + case octo: + return 0444; + default: + break; + } + break; + case hwmon_in: + switch (priv->kind) { + case d5next: + if (channel < 3) + return 0444; + break; + case octo: return 0444; default: break; @@ -154,6 +386,7 @@ static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u3 static int aqc_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { + int ret; struct aqc_data *priv = dev_get_drvdata(dev); if (time_after(jiffies, priv->updated + STATUS_UPDATE_INTERVAL)) @@ -172,6 +405,19 @@ static int aqc_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, case hwmon_power: *val = priv->power_input[channel]; break; + case hwmon_pwm: + switch (priv->kind) { + case octo: + ret = aqc_get_ctrl_val(priv, octo_ctrl_fan_offsets[channel]); + if (ret < 0) + return ret; + + *val = aqc_percent_to_pwm(ret); + break; + default: + break; + } + break; case hwmon_in: *val = priv->voltage_input[channel]; break; @@ -192,48 +438,51 @@ static int aqc_read_string(struct device *dev, enum hwmon_sensor_types type, u32 switch (type) { case hwmon_temp: - switch (priv->kind) { - case d5next: - *str = L_D5NEXT_COOLANT_TEMP; - break; - case farbwerk360: - *str = label_temp_sensors[channel]; - break; - default: - break; - } + *str = priv->temp_label[channel]; break; case hwmon_fan: - switch (priv->kind) { - case d5next: - *str = label_d5next_speeds[channel]; - break; - default: - break; - } + *str = priv->speed_label[channel]; break; case hwmon_power: - switch (priv->kind) { - case d5next: - *str = label_d5next_power[channel]; - break; - default: - break; - } + *str = priv->power_label[channel]; break; case hwmon_in: - switch (priv->kind) { - case d5next: - *str = label_d5next_voltages[channel]; - break; - default: - break; - } + *str = priv->voltage_label[channel]; break; case hwmon_curr: - switch (priv->kind) { - case d5next: - *str = label_d5next_current[channel]; + *str = priv->current_label[channel]; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int aqc_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long val) +{ + int ret, pwm_value; + struct aqc_data *priv = dev_get_drvdata(dev); + + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + switch (priv->kind) { + case octo: + pwm_value = aqc_pwm_to_percent(val); + if (pwm_value < 0) + return pwm_value; + + ret = aqc_set_ctrl_val(priv, octo_ctrl_fan_offsets[channel], + pwm_value); + if (ret < 0) + return ret; + break; + default: + break; + } break; default: break; @@ -250,6 +499,7 @@ static const struct hwmon_ops aqc_hwmon_ops = { .is_visible = aqc_is_visible, .read = aqc_read, .read_string = aqc_read_string, + .write = aqc_write }; static const struct hwmon_channel_info *aqc_info[] = { @@ -259,16 +509,48 @@ static const struct hwmon_channel_info *aqc_info[] = { HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL), HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, HWMON_F_INPUT | HWMON_F_LABEL, HWMON_F_INPUT | HWMON_F_LABEL), HWMON_CHANNEL_INFO(power, + HWMON_P_INPUT | HWMON_P_LABEL, + HWMON_P_INPUT | HWMON_P_LABEL, + HWMON_P_INPUT | HWMON_P_LABEL, + HWMON_P_INPUT | HWMON_P_LABEL, + HWMON_P_INPUT | HWMON_P_LABEL, + HWMON_P_INPUT | HWMON_P_LABEL, HWMON_P_INPUT | HWMON_P_LABEL, HWMON_P_INPUT | HWMON_P_LABEL), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT), HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL), HWMON_CHANNEL_INFO(curr, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL), NULL @@ -279,8 +561,7 @@ static const struct hwmon_chip_info aqc_chip_info = { .info = aqc_info, }; -static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, - int size) +static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { int i, sensor_value; struct aqc_data *priv; @@ -315,6 +596,17 @@ static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 priv->current_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_CURRENT); priv->current_input[1] = get_unaligned_be16(data + D5NEXT_FAN_CURRENT); break; + case farbwerk: + /* Temperature sensor readings */ + for (i = 0; i < FARBWERK_NUM_SENSORS; i++) { + sensor_value = get_unaligned_be16(data + FARBWERK_SENSOR_START + + i * FARBWERK_SENSOR_SIZE); + if (sensor_value == FARBWERK_SENSOR_DISCONNECTED) + priv->temp_input[i] = -ENODATA; + else + priv->temp_input[i] = sensor_value * 10; + } + break; case farbwerk360: /* Temperature sensor readings */ for (i = 0; i < FARBWERK360_NUM_SENSORS; i++) { @@ -326,6 +618,35 @@ static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 priv->temp_input[i] = sensor_value * 10; } break; + case octo: + priv->power_cycles = get_unaligned_be32(data + OCTO_POWER_CYCLES); + + /* Fan speed and related readings */ + for (i = 0; i < OCTO_NUM_FANS; i++) { + priv->speed_input[i] = + get_unaligned_be16(data + octo_sensor_fan_offsets[i] + + OCTO_FAN_SPEED_OFFSET); + priv->power_input[i] = + get_unaligned_be16(data + octo_sensor_fan_offsets[i] + + OCTO_FAN_POWER_OFFSET) * 10000; + priv->voltage_input[i] = + get_unaligned_be16(data + octo_sensor_fan_offsets[i] + + OCTO_FAN_VOLTAGE_OFFSET) * 10; + priv->current_input[i] = + get_unaligned_be16(data + octo_sensor_fan_offsets[i] + + OCTO_FAN_CURRENT_OFFSET); + } + + /* Temperature sensor readings */ + for (i = 0; i < OCTO_NUM_SENSORS; i++) { + sensor_value = get_unaligned_be16(data + OCTO_SENSOR_START + + i * OCTO_SENSOR_SIZE); + if (sensor_value == OCTO_SENSOR_DISCONNECTED) + priv->temp_input[i] = -ENODATA; + else + priv->temp_input[i] = sensor_value * 10; + } + break; default: break; } @@ -378,8 +699,14 @@ static void aqc_debugfs_init(struct aqc_data *priv) debugfs_create_file("serial_number", 0444, priv->debugfs, priv, &serial_number_fops); debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops); - if (priv->kind == d5next) + switch (priv->kind) { + case d5next: + case octo: debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops); + break; + default: + break; + } } #else @@ -419,9 +746,35 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) switch (hdev->product) { case USB_PRODUCT_ID_D5NEXT: priv->kind = d5next; + + priv->temp_label = label_d5next_temp; + priv->speed_label = label_d5next_speeds; + priv->power_label = label_d5next_power; + priv->voltage_label = label_d5next_voltages; + priv->current_label = label_d5next_current; + break; + case USB_PRODUCT_ID_FARBWERK: + priv->kind = farbwerk; + + priv->temp_label = label_temp_sensors; break; case USB_PRODUCT_ID_FARBWERK360: priv->kind = farbwerk360; + + priv->temp_label = label_temp_sensors; + break; + case USB_PRODUCT_ID_OCTO: + priv->kind = octo; + priv->buffer_size = OCTO_CTRL_REPORT_SIZE; + priv->checksum_start = OCTO_CTRL_REPORT_CHECKSUM_START; + priv->checksum_length = OCTO_CTRL_REPORT_CHECKSUM_LENGTH; + priv->checksum_offset = OCTO_CTRL_REPORT_CHECKSUM_OFFSET; + + priv->temp_label = label_temp_sensors; + priv->speed_label = label_fan_speed; + priv->power_label = label_fan_power; + priv->voltage_label = label_fan_voltage; + priv->current_label = label_fan_current; break; default: break; @@ -429,6 +782,14 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->name = aqc_device_names[priv->kind]; + priv->buffer = devm_kzalloc(&hdev->dev, priv->buffer_size, GFP_KERNEL); + if (!priv->buffer) { + ret = -ENOMEM; + goto fail_and_close; + } + + mutex_init(&priv->mutex); + priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, priv->name, priv, &aqc_chip_info, NULL); @@ -461,7 +822,9 @@ static void aqc_remove(struct hid_device *hdev) static const struct hid_device_id aqc_table[] = { { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_D5NEXT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_FARBWERK) }, { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_FARBWERK360) }, + { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_OCTO) }, { } }; @@ -491,4 +854,5 @@ module_exit(aqc_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Aleksa Savic "); +MODULE_AUTHOR("Jack Doan "); MODULE_DESCRIPTION("Hwmon driver for Aquacomputer devices"); diff --git a/drivers/hwmon/as370-hwmon.c b/drivers/hwmon/as370-hwmon.c index 464244ba8d58..63b5b2d6e593 100644 --- a/drivers/hwmon/as370-hwmon.c +++ b/drivers/hwmon/as370-hwmon.c @@ -76,18 +76,8 @@ as370_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, } } -static const u32 as370_hwmon_temp_config[] = { - HWMON_T_INPUT, - 0 -}; - -static const struct hwmon_channel_info as370_hwmon_temp = { - .type = hwmon_temp, - .config = as370_hwmon_temp_config, -}; - static const struct hwmon_channel_info *as370_hwmon_info[] = { - &as370_hwmon_temp, + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), NULL }; diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index b5cf0136360c..57e11b2bab74 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -54,8 +54,10 @@ static char *mutex_path_override; /* ACPI mutex for locking access to the EC for the firmware */ #define ASUS_HW_ACCESS_MUTEX_ASMX "\\AMW0.ASMX" -/* There are two variants of the vendor spelling */ -#define VENDOR_ASUS_UPPER_CASE "ASUSTeK COMPUTER INC." +#define MAX_IDENTICAL_BOARD_VARIATIONS 3 + +/* Moniker for the ACPI global lock (':' is not allowed in ASL identifiers) */ +#define ACPI_GLOBAL_LOCK_PSEUDO_PATH ":GLOBAL_LOCK" typedef union { u32 value; @@ -133,8 +135,44 @@ enum ec_sensors { #define SENSOR_TEMP_WATER_IN BIT(ec_sensor_temp_water_in) #define SENSOR_TEMP_WATER_OUT BIT(ec_sensor_temp_water_out) +enum board_family { + family_unknown, + family_amd_400_series, + family_amd_500_series, +}; + /* All the known sensors for ASUS EC controllers */ -static const struct ec_sensor_info known_ec_sensors[] = { +static const struct ec_sensor_info sensors_family_amd_400[] = { + [ec_sensor_temp_chipset] = + EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a), + [ec_sensor_temp_cpu] = + EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b), + [ec_sensor_temp_mb] = + EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x3c), + [ec_sensor_temp_t_sensor] = + EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d), + [ec_sensor_temp_vrm] = + EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e), + [ec_sensor_in_cpu_core] = + EC_SENSOR("CPU Core", hwmon_in, 2, 0x00, 0xa2), + [ec_sensor_fan_cpu_opt] = + EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xbc), + [ec_sensor_fan_vrm_hs] = + EC_SENSOR("VRM HS", hwmon_fan, 2, 0x00, 0xb2), + [ec_sensor_fan_chipset] = + /* no chipset fans in this generation */ + EC_SENSOR("Chipset", hwmon_fan, 0, 0x00, 0x00), + [ec_sensor_fan_water_flow] = + EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xb4), + [ec_sensor_curr_cpu] = + EC_SENSOR("CPU", hwmon_curr, 1, 0x00, 0xf4), + [ec_sensor_temp_water_in] = + EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x0d), + [ec_sensor_temp_water_out] = + EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x0b), +}; + +static const struct ec_sensor_info sensors_family_amd_500[] = { [ec_sensor_temp_chipset] = EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a), [ec_sensor_temp_cpu] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b), @@ -164,68 +202,134 @@ static const struct ec_sensor_info known_ec_sensors[] = { (SENSOR_TEMP_CHIPSET | SENSOR_TEMP_CPU | SENSOR_TEMP_MB) #define SENSOR_SET_TEMP_WATER (SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT) -#define DMI_EXACT_MATCH_BOARD(vendor, name, sensors) { \ - .matches = { \ - DMI_EXACT_MATCH(DMI_BOARD_VENDOR, vendor), \ - DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \ - }, \ - .driver_data = (void *)(sensors), \ -} +struct ec_board_info { + const char *board_names[MAX_IDENTICAL_BOARD_VARIATIONS]; + unsigned long sensors; + /* + * Defines which mutex to use for guarding access to the state and the + * hardware. Can be either a full path to an AML mutex or the + * pseudo-path ACPI_GLOBAL_LOCK_PSEUDO_PATH to use the global ACPI lock, + * or left empty to use a regular mutex object, in which case access to + * the hardware is not guarded. + */ + const char *mutex_path; + enum board_family family; +}; -static const struct dmi_system_id asus_ec_dmi_table[] __initconst = { - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "PRIME X570-PRO", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | - SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "Pro WS X570-ACE", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | - SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, - "ROG CROSSHAIR VIII DARK HERO", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | - SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW | - SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, - "ROG CROSSHAIR VIII FORMULA", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | - SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG CROSSHAIR VIII HERO", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | - SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | - SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, - "ROG CROSSHAIR VIII HERO (WI-FI)", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | - SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | - SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, - "ROG CROSSHAIR VIII IMPACT", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_FAN_CHIPSET | - SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX B550-E GAMING", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | - SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_FAN_CPU_OPT), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX B550-I GAMING", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | - SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_FAN_VRM_HS | - SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-E GAMING", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | - SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_FAN_CHIPSET | - SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-F GAMING", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | - SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-I GAMING", - SENSOR_TEMP_T_SENSOR | SENSOR_FAN_VRM_HS | - SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), +static const struct ec_board_info board_info[] = { + { + .board_names = {"PRIME X470-PRO"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_FAN_CPU_OPT | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH, + .family = family_amd_400_series, + }, + { + .board_names = {"PRIME X570-PRO"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, + }, + { + .board_names = {"ProArt X570-CREATOR WIFI"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + }, + { + .board_names = {"Pro WS X570-ACE"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, + }, + { + .board_names = {"ROG CROSSHAIR VIII DARK HERO"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | + SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, + }, + { + .board_names = { + "ROG CROSSHAIR VIII FORMULA" + "ROG CROSSHAIR VIII HERO", + "ROG CROSSHAIR VIII HERO (WI-FI)", + }, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | + SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | + SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | + SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, + }, + { + .board_names = {"ROG CROSSHAIR VIII IMPACT"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | + SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, + }, + { + .board_names = {"ROG STRIX B550-E GAMING"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_FAN_CPU_OPT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, + }, + { + .board_names = {"ROG STRIX B550-I GAMING"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_FAN_VRM_HS | SENSOR_CURR_CPU | + SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, + }, + { + .board_names = {"ROG STRIX X570-E GAMING"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | + SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, + }, + { + .board_names = {"ROG STRIX X570-E GAMING WIFI II"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_CURR_CPU | + SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, + }, + { + .board_names = {"ROG STRIX X570-F GAMING"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, + }, + { + .board_names = {"ROG STRIX X570-I GAMING"}, + .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_FAN_VRM_HS | + SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | + SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, + }, {} }; @@ -234,8 +338,49 @@ struct ec_sensor { s32 cached_value; }; +struct lock_data { + union { + acpi_handle aml; + /* global lock handle */ + u32 glk; + } mutex; + bool (*lock)(struct lock_data *data); + bool (*unlock)(struct lock_data *data); +}; + +/* + * The next function pairs implement options for locking access to the + * state and the EC + */ +static bool lock_via_acpi_mutex(struct lock_data *data) +{ + /* + * ASUS DSDT does not specify that access to the EC has to be guarded, + * but firmware does access it via ACPI + */ + return ACPI_SUCCESS(acpi_acquire_mutex(data->mutex.aml, + NULL, ACPI_LOCK_DELAY_MS)); +} + +static bool unlock_acpi_mutex(struct lock_data *data) +{ + return ACPI_SUCCESS(acpi_release_mutex(data->mutex.aml, NULL)); +} + +static bool lock_via_global_acpi_lock(struct lock_data *data) +{ + return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS, + &data->mutex.glk)); +} + +static bool unlock_global_acpi_lock(struct lock_data *data) +{ + return ACPI_SUCCESS(acpi_release_global_lock(data->mutex.glk)); +} + struct ec_sensors_data { - unsigned long board_sensors; + const struct ec_board_info *board_info; + const struct ec_sensor_info *sensors_info; struct ec_sensor *sensors; /* EC registers to read from */ u16 *registers; @@ -244,7 +389,7 @@ struct ec_sensors_data { u8 banks[ASUS_EC_MAX_BANK + 1]; /* in jiffies */ unsigned long last_updated; - acpi_handle aml_mutex; + struct lock_data lock_data; /* number of board EC sensors */ u8 nr_sensors; /* @@ -278,7 +423,7 @@ static bool is_sensor_data_signed(const struct ec_sensor_info *si) static const struct ec_sensor_info * get_sensor_info(const struct ec_sensors_data *state, int index) { - return &known_ec_sensors[state->sensors[index].info_index]; + return state->sensors_info + state->sensors[index].info_index; } static int find_ec_sensor_index(const struct ec_sensors_data *ec, @@ -301,11 +446,6 @@ static int __init bank_compare(const void *a, const void *b) return *((const s8 *)a) - *((const s8 *)b); } -static int __init board_sensors_count(unsigned long sensors) -{ - return hweight_long(sensors); -} - static void __init setup_sensor_data(struct ec_sensors_data *ec) { struct ec_sensor *s = ec->sensors; @@ -316,14 +456,14 @@ static void __init setup_sensor_data(struct ec_sensors_data *ec) ec->nr_banks = 0; ec->nr_registers = 0; - for_each_set_bit(i, &ec->board_sensors, - BITS_PER_TYPE(ec->board_sensors)) { + for_each_set_bit(i, &ec->board_info->sensors, + BITS_PER_TYPE(ec->board_info->sensors)) { s->info_index = i; s->cached_value = 0; ec->nr_registers += - known_ec_sensors[s->info_index].addr.components.size; + ec->sensors_info[s->info_index].addr.components.size; bank_found = false; - bank = known_ec_sensors[s->info_index].addr.components.bank; + bank = ec->sensors_info[s->info_index].addr.components.bank; for (j = 0; j < ec->nr_banks; j++) { if (ec->banks[j] == bank) { bank_found = true; @@ -353,23 +493,36 @@ static void __init fill_ec_registers(struct ec_sensors_data *ec) } } -static acpi_handle __init asus_hw_access_mutex(struct device *dev) +static int __init setup_lock_data(struct device *dev) { const char *mutex_path; - acpi_handle res; int status; + struct ec_sensors_data *state = dev_get_drvdata(dev); mutex_path = mutex_path_override ? - mutex_path_override : ASUS_HW_ACCESS_MUTEX_ASMX; + mutex_path_override : state->board_info->mutex_path; - status = acpi_get_handle(NULL, (acpi_string)mutex_path, &res); - if (ACPI_FAILURE(status)) { - dev_err(dev, - "Could not get hardware access guard mutex '%s': error %d", - mutex_path, status); - return NULL; + if (!mutex_path || !strlen(mutex_path)) { + dev_err(dev, "Hardware access guard mutex name is empty"); + return -EINVAL; } - return res; + if (!strcmp(mutex_path, ACPI_GLOBAL_LOCK_PSEUDO_PATH)) { + state->lock_data.mutex.glk = 0; + state->lock_data.lock = lock_via_global_acpi_lock; + state->lock_data.unlock = unlock_global_acpi_lock; + } else { + status = acpi_get_handle(NULL, (acpi_string)mutex_path, + &state->lock_data.mutex.aml); + if (ACPI_FAILURE(status)) { + dev_err(dev, + "Failed to get hardware access guard AML mutex '%s': error %d", + mutex_path, status); + return -ENOENT; + } + state->lock_data.lock = lock_via_acpi_mutex; + state->lock_data.unlock = unlock_acpi_mutex; + } + return 0; } static int asus_ec_bank_switch(u8 bank, u8 *old) @@ -457,10 +610,11 @@ static inline s32 get_sensor_value(const struct ec_sensor_info *si, u8 *data) static void update_sensor_values(struct ec_sensors_data *ec, u8 *data) { const struct ec_sensor_info *si; - struct ec_sensor *s; + struct ec_sensor *s, *sensor_end; - for (s = ec->sensors; s != ec->sensors + ec->nr_sensors; s++) { - si = &known_ec_sensors[s->info_index]; + sensor_end = ec->sensors + ec->nr_sensors; + for (s = ec->sensors; s != sensor_end; s++) { + si = ec->sensors_info + s->info_index; s->cached_value = get_sensor_value(si, data); data += si->addr.components.size; } @@ -471,15 +625,9 @@ static int update_ec_sensors(const struct device *dev, { int status; - /* - * ASUS DSDT does not specify that access to the EC has to be guarded, - * but firmware does access it via ACPI - */ - if (ACPI_FAILURE(acpi_acquire_mutex(ec->aml_mutex, NULL, - ACPI_LOCK_DELAY_MS))) { - dev_err(dev, "Failed to acquire AML mutex"); - status = -EBUSY; - goto cleanup; + if (!ec->lock_data.lock(&ec->lock_data)) { + dev_warn(dev, "Failed to acquire mutex"); + return -EBUSY; } status = asus_ec_block_read(dev, ec); @@ -487,10 +635,10 @@ static int update_ec_sensors(const struct device *dev, if (!status) { update_sensor_values(ec, ec->read_buffer); } - if (ACPI_FAILURE(acpi_release_mutex(ec->aml_mutex, NULL))) { - dev_err(dev, "Failed to release AML mutex"); - } -cleanup: + + if (!ec->lock_data.unlock(&ec->lock_data)) + dev_err(dev, "Failed to release mutex"); + return status; } @@ -597,12 +745,24 @@ static struct hwmon_chip_info asus_ec_chip_info = { .ops = &asus_ec_hwmon_ops, }; -static unsigned long __init get_board_sensors(void) +static const struct ec_board_info * __init get_board_info(void) { - const struct dmi_system_id *dmi_entry = - dmi_first_match(asus_ec_dmi_table); + const char *dmi_board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + const char *dmi_board_name = dmi_get_system_info(DMI_BOARD_NAME); + const struct ec_board_info *board; - return dmi_entry ? (unsigned long)dmi_entry->driver_data : 0; + if (!dmi_board_vendor || !dmi_board_name || + strcasecmp(dmi_board_vendor, "ASUSTeK COMPUTER INC.")) + return NULL; + + for (board = board_info; board->sensors; board++) { + if (match_string(board->board_names, + MAX_IDENTICAL_BOARD_VARIATIONS, + dmi_board_name) >= 0) + return board; + } + + return NULL; } static int __init asus_ec_probe(struct platform_device *pdev) @@ -610,17 +770,18 @@ static int __init asus_ec_probe(struct platform_device *pdev) const struct hwmon_channel_info **ptr_asus_ec_ci; int nr_count[hwmon_max] = { 0 }, nr_types = 0; struct hwmon_channel_info *asus_ec_hwmon_chan; + const struct ec_board_info *pboard_info; const struct hwmon_chip_info *chip_info; struct device *dev = &pdev->dev; struct ec_sensors_data *ec_data; const struct ec_sensor_info *si; enum hwmon_sensor_types type; - unsigned long board_sensors; struct device *hwdev; unsigned int i; + int status; - board_sensors = get_board_sensors(); - if (!board_sensors) + pboard_info = get_board_info(); + if (!pboard_info) return -ENODEV; ec_data = devm_kzalloc(dev, sizeof(struct ec_sensors_data), @@ -629,11 +790,31 @@ static int __init asus_ec_probe(struct platform_device *pdev) return -ENOMEM; dev_set_drvdata(dev, ec_data); - ec_data->board_sensors = board_sensors; - ec_data->nr_sensors = board_sensors_count(ec_data->board_sensors); + ec_data->board_info = pboard_info; + + switch (ec_data->board_info->family) { + case family_amd_400_series: + ec_data->sensors_info = sensors_family_amd_400; + break; + case family_amd_500_series: + ec_data->sensors_info = sensors_family_amd_500; + break; + default: + dev_err(dev, "Unknown board family: %d", + ec_data->board_info->family); + return -EINVAL; + } + + ec_data->nr_sensors = hweight_long(ec_data->board_info->sensors); ec_data->sensors = devm_kcalloc(dev, ec_data->nr_sensors, sizeof(struct ec_sensor), GFP_KERNEL); + status = setup_lock_data(dev); + if (status) { + dev_err(dev, "Failed to setup state/EC locking: %d", status); + return status; + } + setup_sensor_data(ec_data); ec_data->registers = devm_kcalloc(dev, ec_data->nr_registers, sizeof(u16), GFP_KERNEL); @@ -645,8 +826,6 @@ static int __init asus_ec_probe(struct platform_device *pdev) fill_ec_registers(ec_data); - ec_data->aml_mutex = asus_hw_access_mutex(dev); - for (i = 0; i < ec_data->nr_sensors; ++i) { si = get_sensor_info(ec_data, i); if (!nr_count[si->type]) @@ -703,7 +882,14 @@ static struct platform_driver asus_ec_sensors_platform_driver = { }, }; -MODULE_DEVICE_TABLE(dmi, asus_ec_dmi_table); +MODULE_DEVICE_TABLE(acpi, acpi_ec_ids); +/* + * we use module_platform_driver_probe() rather than module_platform_driver() + * because the probe function (and its dependants) are marked with __init, which + * means we can't put it into the .probe member of the platform_driver struct + * above, and we can't mark the asus_ec_sensors_platform_driver object as __init + * because the object is referenced from the module exit code. + */ module_platform_driver_probe(asus_ec_sensors_platform_driver, asus_ec_probe); module_param_named(mutex_path, mutex_path_override, charp, 0); diff --git a/drivers/hwmon/bt1-pvt.c b/drivers/hwmon/bt1-pvt.c index 74ce5211eb75..21ab172774ec 100644 --- a/drivers/hwmon/bt1-pvt.c +++ b/drivers/hwmon/bt1-pvt.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -65,7 +66,7 @@ static const struct pvt_sensor_info pvt_info[] = { * 48380, * where T = [-48380, 147438] mC and N = [0, 1023]. */ -static const struct pvt_poly __maybe_unused poly_temp_to_N = { +static const struct polynomial __maybe_unused poly_temp_to_N = { .total_divider = 10000, .terms = { {4, 18322, 10000, 10000}, @@ -76,7 +77,7 @@ static const struct pvt_poly __maybe_unused poly_temp_to_N = { } }; -static const struct pvt_poly poly_N_to_temp = { +static const struct polynomial poly_N_to_temp = { .total_divider = 1, .terms = { {4, -16743, 1000, 1}, @@ -97,7 +98,7 @@ static const struct pvt_poly poly_N_to_temp = { * N = (18658e-3*V - 11572) / 10, * V = N * 10^5 / 18658 + 11572 * 10^4 / 18658. */ -static const struct pvt_poly __maybe_unused poly_volt_to_N = { +static const struct polynomial __maybe_unused poly_volt_to_N = { .total_divider = 10, .terms = { {1, 18658, 1000, 1}, @@ -105,7 +106,7 @@ static const struct pvt_poly __maybe_unused poly_volt_to_N = { } }; -static const struct pvt_poly poly_N_to_volt = { +static const struct polynomial poly_N_to_volt = { .total_divider = 10, .terms = { {1, 100000, 18658, 1}, @@ -113,31 +114,6 @@ static const struct pvt_poly poly_N_to_volt = { } }; -/* - * Here is the polynomial calculation function, which performs the - * redistributed terms calculations. It's pretty straightforward. We walk - * over each degree term up to the free one, and perform the redistributed - * multiplication of the term coefficient, its divider (as for the rationale - * fraction representation), data power and the rational fraction divider - * leftover. Then all of this is collected in a total sum variable, which - * value is normalized by the total divider before being returned. - */ -static long pvt_calc_poly(const struct pvt_poly *poly, long data) -{ - const struct pvt_poly_term *term = poly->terms; - long tmp, ret = 0; - int deg; - - do { - tmp = term->coef; - for (deg = 0; deg < term->deg; ++deg) - tmp = mult_frac(tmp, data, term->divider); - ret += tmp / term->divider_leftover; - } while ((term++)->deg); - - return ret / poly->total_divider; -} - static inline u32 pvt_update(void __iomem *reg, u32 mask, u32 data) { u32 old; @@ -324,9 +300,9 @@ static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type, } while (read_seqretry(&cache->data_seqlock, seq)); if (type == PVT_TEMP) - *val = pvt_calc_poly(&poly_N_to_temp, data); + *val = polynomial_calc(&poly_N_to_temp, data); else - *val = pvt_calc_poly(&poly_N_to_volt, data); + *val = polynomial_calc(&poly_N_to_volt, data); return 0; } @@ -345,9 +321,9 @@ static int pvt_read_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, data = FIELD_GET(PVT_THRES_HI_MASK, data); if (type == PVT_TEMP) - *val = pvt_calc_poly(&poly_N_to_temp, data); + *val = polynomial_calc(&poly_N_to_temp, data); else - *val = pvt_calc_poly(&poly_N_to_volt, data); + *val = polynomial_calc(&poly_N_to_volt, data); return 0; } @@ -360,10 +336,10 @@ static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, if (type == PVT_TEMP) { val = clamp(val, PVT_TEMP_MIN, PVT_TEMP_MAX); - data = pvt_calc_poly(&poly_temp_to_N, val); + data = polynomial_calc(&poly_temp_to_N, val); } else { val = clamp(val, PVT_VOLT_MIN, PVT_VOLT_MAX); - data = pvt_calc_poly(&poly_volt_to_N, val); + data = polynomial_calc(&poly_volt_to_N, val); } /* Serialize limit update, since a part of the register is changed. */ @@ -522,9 +498,9 @@ static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type, return -ETIMEDOUT; if (type == PVT_TEMP) - *val = pvt_calc_poly(&poly_N_to_temp, data); + *val = polynomial_calc(&poly_N_to_temp, data); else - *val = pvt_calc_poly(&poly_N_to_volt, data); + *val = polynomial_calc(&poly_N_to_volt, data); return 0; } diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 84cb1ede7bc0..071aa6f4e109 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -21,14 +21,17 @@ #include #include #include +#include #include #include #include #include #include #include -#include +#include #include +#include +#include #include #include @@ -46,8 +49,11 @@ #define I8K_SMM_GET_DELL_SIG1 0xfea3 #define I8K_SMM_GET_DELL_SIG2 0xffa3 +/* in usecs */ +#define DELL_SMM_MAX_DURATION 250000 + #define I8K_FAN_MULT 30 -#define I8K_FAN_MAX_RPM 30000 +#define I8K_FAN_RPM_THRESHOLD 1000 #define I8K_MAX_TEMP 127 #define I8K_FN_NONE 0x00 @@ -80,6 +86,11 @@ struct dell_smm_data { int *fan_nominal_speed[DELL_SMM_NO_FANS]; }; +struct dell_smm_cooling_data { + u8 fan_num; + struct dell_smm_data *data; +}; + MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)"); MODULE_AUTHOR("Pali Rohár "); MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver"); @@ -231,6 +242,9 @@ static int i8k_smm_func(void *par) pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x (took %7lld usecs)\n", eax, ebx, (rc ? 0xffff : regs->eax & 0xffff), duration); + if (duration > DELL_SMM_MAX_DURATION) + pr_warn_once("SMM call took %lld usecs!\n", duration); + return rc; } @@ -318,7 +332,7 @@ static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, u8 if (data->disallow_fan_support) return -EINVAL; - return i8k_smm(®s) ? : (regs.eax & 0xffff) * data->i8k_fan_mult; + return i8k_smm(®s) ? : (regs.eax & 0xffff); } /* @@ -638,9 +652,50 @@ static void __init i8k_init_procfs(struct device *dev) #endif -/* - * Hwmon interface - */ +static int dell_smm_get_max_state(struct thermal_cooling_device *dev, unsigned long *state) +{ + struct dell_smm_cooling_data *cdata = dev->devdata; + + *state = cdata->data->i8k_fan_max; + + return 0; +} + +static int dell_smm_get_cur_state(struct thermal_cooling_device *dev, unsigned long *state) +{ + struct dell_smm_cooling_data *cdata = dev->devdata; + int ret; + + ret = i8k_get_fan_status(cdata->data, cdata->fan_num); + if (ret < 0) + return ret; + + *state = ret; + + return 0; +} + +static int dell_smm_set_cur_state(struct thermal_cooling_device *dev, unsigned long state) +{ + struct dell_smm_cooling_data *cdata = dev->devdata; + struct dell_smm_data *data = cdata->data; + int ret; + + if (state > data->i8k_fan_max) + return -EINVAL; + + mutex_lock(&data->i8k_mutex); + ret = i8k_set_fan(data, cdata->fan_num, (int)state); + mutex_unlock(&data->i8k_mutex); + + return ret; +} + +static const struct thermal_cooling_device_ops dell_smm_cooling_ops = { + .get_max_state = dell_smm_get_max_state, + .get_cur_state = dell_smm_get_cur_state, + .set_cur_state = dell_smm_set_cur_state, +}; static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr, int channel) @@ -727,6 +782,7 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a long *val) { struct dell_smm_data *data = dev_get_drvdata(dev); + int mult = data->i8k_fan_mult; int ret; switch (type) { @@ -755,11 +811,11 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a return 0; case hwmon_fan_min: - *val = data->fan_nominal_speed[channel][0]; + *val = data->fan_nominal_speed[channel][0] * mult; return 0; case hwmon_fan_max: - *val = data->fan_nominal_speed[channel][data->i8k_fan_max]; + *val = data->fan_nominal_speed[channel][data->i8k_fan_max] * mult; return 0; case hwmon_fan_target: @@ -770,7 +826,7 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a if (ret > data->i8k_fan_max) ret = data->i8k_fan_max; - *val = data->fan_nominal_speed[channel][ret]; + *val = data->fan_nominal_speed[channel][ret] * mult; return 0; default: @@ -941,6 +997,37 @@ static const struct hwmon_chip_info dell_smm_chip_info = { .info = dell_smm_info, }; +static int __init dell_smm_init_cdev(struct device *dev, u8 fan_num) +{ + struct dell_smm_data *data = dev_get_drvdata(dev); + struct thermal_cooling_device *cdev; + struct dell_smm_cooling_data *cdata; + int ret = 0; + char *name; + + name = kasprintf(GFP_KERNEL, "dell-smm-fan%u", fan_num + 1); + if (!name) + return -ENOMEM; + + cdata = devm_kmalloc(dev, sizeof(*cdata), GFP_KERNEL); + if (cdata) { + cdata->fan_num = fan_num; + cdata->data = data; + cdev = devm_thermal_of_cooling_device_register(dev, NULL, name, cdata, + &dell_smm_cooling_ops); + if (IS_ERR(cdev)) { + devm_kfree(dev, cdata); + ret = PTR_ERR(cdev); + } + } else { + ret = -ENOMEM; + } + + kfree(name); + + return ret; +} + static int __init dell_smm_init_hwmon(struct device *dev) { struct dell_smm_data *data = dev_get_drvdata(dev); @@ -967,6 +1054,15 @@ static int __init dell_smm_init_hwmon(struct device *dev) continue; data->fan[i] = true; + + /* the cooling device is not critical, ignore failures */ + if (IS_REACHABLE(CONFIG_THERMAL)) { + err = dell_smm_init_cdev(dev, i); + if (err < 0) + dev_warn(dev, "Failed to register cooling device for fan %u\n", + i + 1); + } + data->fan_nominal_speed[i] = devm_kmalloc_array(dev, data->i8k_fan_max + 1, sizeof(*data->fan_nominal_speed[i]), GFP_KERNEL); @@ -982,6 +1078,13 @@ static int __init dell_smm_init_hwmon(struct device *dev) break; } data->fan_nominal_speed[i][state] = err; + /* + * Autodetect fan multiplier based on nominal rpm if multiplier + * was not specified as module param or in DMI. If fan reports + * rpm value too high then set multiplier to 1. + */ + if (!fan_mult && err > I8K_FAN_RPM_THRESHOLD) + data->i8k_fan_mult = 1; } } @@ -1270,15 +1373,12 @@ static int __init dell_smm_probe(struct platform_device *pdev) struct dell_smm_data *data; const struct dmi_system_id *id, *fan_control; int ret; - u8 fan; data = devm_kzalloc(&pdev->dev, sizeof(struct dell_smm_data), GFP_KERNEL); if (!data) return -ENOMEM; mutex_init(&data->i8k_mutex); - data->i8k_fan_mult = I8K_FAN_MULT; - data->i8k_fan_max = I8K_FAN_HIGH; platform_set_drvdata(pdev, data); if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) { @@ -1313,7 +1413,9 @@ static int __init dell_smm_probe(struct platform_device *pdev) fan_max = conf->fan_max; } - data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH; /* Must not be 0 */ + /* All options must not be 0 */ + data->i8k_fan_mult = fan_mult ? : I8K_FAN_MULT; + data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH; data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max); fan_control = dmi_first_match(i8k_whitelist_fan_control); @@ -1325,25 +1427,6 @@ static int __init dell_smm_probe(struct platform_device *pdev) dev_info(&pdev->dev, "enabling support for setting automatic/manual fan control\n"); } - if (!fan_mult) { - /* - * Autodetect fan multiplier based on nominal rpm - * If fan reports rpm value too high then set multiplier to 1 - */ - for (fan = 0; fan < DELL_SMM_NO_FANS; ++fan) { - ret = i8k_get_fan_nominal_speed(data, fan, data->i8k_fan_max); - if (ret < 0) - continue; - - if (ret > I8K_FAN_MAX_RPM) - data->i8k_fan_mult = 1; - break; - } - } else { - /* Fan multiplier was specified in module param or in dmi */ - data->i8k_fan_mult = fan_mult; - } - ret = dell_smm_init_hwmon(&pdev->dev); if (ret) return ret; diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 989e2c8496dd..2e2cd79d89eb 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -764,7 +764,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, "hwmon: '%s' is not a valid name attribute, please fix\n", name); - id = ida_simple_get(&hwmon_ida, 0, 0, GFP_KERNEL); + id = ida_alloc(&hwmon_ida, GFP_KERNEL); if (id < 0) return ERR_PTR(id); @@ -856,7 +856,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, free_hwmon: hwmon_dev_release(hdev); ida_remove: - ida_simple_remove(&hwmon_ida, id); + ida_free(&hwmon_ida, id); return ERR_PTR(err); } @@ -886,11 +886,12 @@ EXPORT_SYMBOL_GPL(hwmon_device_register_with_groups); /** * hwmon_device_register_with_info - register w/ hwmon - * @dev: the parent device - * @name: hwmon name attribute - * @drvdata: driver data to attach to created device - * @chip: pointer to hwmon chip information + * @dev: the parent device (mandatory) + * @name: hwmon name attribute (mandatory) + * @drvdata: driver data to attach to created device (optional) + * @chip: pointer to hwmon chip information (mandatory) * @extra_groups: pointer to list of additional non-standard attribute groups + * (optional) * * hwmon_device_unregister() must be called when the device is no * longer needed. @@ -903,19 +904,41 @@ hwmon_device_register_with_info(struct device *dev, const char *name, const struct hwmon_chip_info *chip, const struct attribute_group **extra_groups) { - if (!name) + if (!dev || !name || !chip) return ERR_PTR(-EINVAL); - if (chip && (!chip->ops || !chip->ops->is_visible || !chip->info)) - return ERR_PTR(-EINVAL); - - if (chip && !dev) + if (!chip->ops || !chip->ops->is_visible || !chip->info) return ERR_PTR(-EINVAL); return __hwmon_device_register(dev, name, drvdata, chip, extra_groups); } EXPORT_SYMBOL_GPL(hwmon_device_register_with_info); +/** + * hwmon_device_register_for_thermal - register hwmon device for thermal subsystem + * @dev: the parent device + * @name: hwmon name attribute + * @drvdata: driver data to attach to created device + * + * The use of this function is restricted. It is provided for legacy reasons + * and must only be called from the thermal subsystem. + * + * hwmon_device_unregister() must be called when the device is no + * longer needed. + * + * Returns the pointer to the new device. + */ +struct device * +hwmon_device_register_for_thermal(struct device *dev, const char *name, + void *drvdata) +{ + if (!name || !dev) + return ERR_PTR(-EINVAL); + + return __hwmon_device_register(dev, name, drvdata, NULL, NULL); +} +EXPORT_SYMBOL_NS_GPL(hwmon_device_register_for_thermal, HWMON_THERMAL); + /** * hwmon_device_register - register w/ hwmon * @dev: the device to register @@ -945,7 +968,7 @@ void hwmon_device_unregister(struct device *dev) if (likely(sscanf(dev_name(dev), HWMON_ID_FORMAT, &id) == 1)) { device_unregister(dev); - ida_simple_remove(&hwmon_ida, id); + ida_free(&hwmon_ida, id); } else dev_dbg(dev->parent, "hwmon_device_unregister() failed: bad class ID!\n"); @@ -1057,6 +1080,59 @@ void devm_hwmon_device_unregister(struct device *dev) } EXPORT_SYMBOL_GPL(devm_hwmon_device_unregister); +static char *__hwmon_sanitize_name(struct device *dev, const char *old_name) +{ + char *name, *p; + + if (dev) + name = devm_kstrdup(dev, old_name, GFP_KERNEL); + else + name = kstrdup(old_name, GFP_KERNEL); + if (!name) + return ERR_PTR(-ENOMEM); + + for (p = name; *p; p++) + if (hwmon_is_bad_char(*p)) + *p = '_'; + + return name; +} + +/** + * hwmon_sanitize_name - Replaces invalid characters in a hwmon name + * @name: NUL-terminated name + * + * Allocates a new string where any invalid characters will be replaced + * by an underscore. It is the responsibility of the caller to release + * the memory. + * + * Returns newly allocated name, or ERR_PTR on error. + */ +char *hwmon_sanitize_name(const char *name) +{ + return __hwmon_sanitize_name(NULL, name); +} +EXPORT_SYMBOL_GPL(hwmon_sanitize_name); + +/** + * devm_hwmon_sanitize_name - resource managed hwmon_sanitize_name() + * @dev: device to allocate memory for + * @name: NUL-terminated name + * + * Allocates a new string where any invalid characters will be replaced + * by an underscore. + * + * Returns newly allocated name, or ERR_PTR on error. + */ +char *devm_hwmon_sanitize_name(struct device *dev, const char *name) +{ + if (!dev) + return ERR_PTR(-EINVAL); + + return __hwmon_sanitize_name(dev, name); +} +EXPORT_SYMBOL_GPL(devm_hwmon_sanitize_name); + static void __init hwmon_pci_quirks(void) { #if defined CONFIG_X86 && defined CONFIG_PCI diff --git a/drivers/hwmon/ibmaem.c b/drivers/hwmon/ibmaem.c index de6baf6ca3d1..5c4cf742f5ae 100644 --- a/drivers/hwmon/ibmaem.c +++ b/drivers/hwmon/ibmaem.c @@ -482,7 +482,7 @@ static void aem_delete(struct aem_data *data) ipmi_destroy_user(data->ipmi.user); platform_set_drvdata(data->pdev, NULL); platform_device_unregister(data->pdev); - ida_simple_remove(&aem_ida, data->id); + ida_free(&aem_ida, data->id); kfree(data); } @@ -539,7 +539,7 @@ static int aem_init_aem1_inst(struct aem_ipmi_data *probe, u8 module_handle) data->power_period[i] = AEM_DEFAULT_POWER_INTERVAL; /* Create sub-device for this fw instance */ - data->id = ida_simple_get(&aem_ida, 0, 0, GFP_KERNEL); + data->id = ida_alloc(&aem_ida, GFP_KERNEL); if (data->id < 0) goto id_err; @@ -600,7 +600,7 @@ ipmi_err: platform_set_drvdata(data->pdev, NULL); platform_device_unregister(data->pdev); dev_err: - ida_simple_remove(&aem_ida, data->id); + ida_free(&aem_ida, data->id); id_err: kfree(data); @@ -679,7 +679,7 @@ static int aem_init_aem2_inst(struct aem_ipmi_data *probe, data->power_period[i] = AEM_DEFAULT_POWER_INTERVAL; /* Create sub-device for this fw instance */ - data->id = ida_simple_get(&aem_ida, 0, 0, GFP_KERNEL); + data->id = ida_alloc(&aem_ida, GFP_KERNEL); if (data->id < 0) goto id_err; @@ -740,7 +740,7 @@ ipmi_err: platform_set_drvdata(data->pdev, NULL); platform_device_unregister(data->pdev); dev_err: - ida_simple_remove(&aem_ida, data->id); + ida_free(&aem_ida, data->id); id_err: kfree(data); diff --git a/drivers/hwmon/intel-m10-bmc-hwmon.c b/drivers/hwmon/intel-m10-bmc-hwmon.c index 7a08e4c44a4b..6e82f7200d1c 100644 --- a/drivers/hwmon/intel-m10-bmc-hwmon.c +++ b/drivers/hwmon/intel-m10-bmc-hwmon.c @@ -515,7 +515,6 @@ static int m10bmc_hwmon_probe(struct platform_device *pdev) struct intel_m10bmc *m10bmc = dev_get_drvdata(pdev->dev.parent); struct device *hwmon_dev, *dev = &pdev->dev; struct m10bmc_hwmon *hw; - int i; hw = devm_kzalloc(dev, sizeof(*hw), GFP_KERNEL); if (!hw) @@ -528,13 +527,9 @@ static int m10bmc_hwmon_probe(struct platform_device *pdev) hw->chip.info = hw->bdata->hinfo; hw->chip.ops = &m10bmc_hwmon_ops; - hw->hw_name = devm_kstrdup(dev, id->name, GFP_KERNEL); - if (!hw->hw_name) - return -ENOMEM; - - for (i = 0; hw->hw_name[i]; i++) - if (hwmon_is_bad_char(hw->hw_name[i])) - hw->hw_name[i] = '_'; + hw->hw_name = devm_hwmon_sanitize_name(dev, id->name); + if (IS_ERR(hw->hw_name)) + return PTR_ERR(hw->hw_name); hwmon_dev = devm_hwmon_device_register_with_info(dev, hw->hw_name, hw, &hw->chip, NULL); diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c index cb347a6bd8d9..07f7f8b5b73d 100644 --- a/drivers/hwmon/jc42.c +++ b/drivers/hwmon/jc42.c @@ -63,6 +63,7 @@ static const unsigned short normal_i2c[] = { #define STM_MANID 0x104a /* ST Microelectronics */ #define GT_MANID 0x1c68 /* Giantec */ #define GT_MANID2 0x132d /* Giantec, 2nd mfg ID */ +#define SI_MANID 0x1c85 /* Seiko Instruments */ /* SMBUS register */ #define SMBUS_STMOUT BIT(7) /* SMBus time-out, active low */ @@ -156,6 +157,10 @@ static const unsigned short normal_i2c[] = { #define STTS3000_DEVID 0x0200 #define STTS3000_DEVID_MASK 0xffff +/* Seiko Instruments */ +#define S34TS04A_DEVID 0x2221 +#define S34TS04A_DEVID_MASK 0xffff + static u16 jc42_hysteresis[] = { 0, 1500, 3000, 6000 }; struct jc42_chips { @@ -186,6 +191,7 @@ static struct jc42_chips jc42_chips[] = { { ONS_MANID, CAT34TS04_DEVID, CAT34TS04_DEVID_MASK }, { ONS_MANID, N34TS04_DEVID, N34TS04_DEVID_MASK }, { NXP_MANID, SE98_DEVID, SE98_DEVID_MASK }, + { SI_MANID, S34TS04A_DEVID, S34TS04A_DEVID_MASK }, { STM_MANID, STTS424_DEVID, STTS424_DEVID_MASK }, { STM_MANID, STTS424E_DEVID, STTS424E_DEVID_MASK }, { STM_MANID, STTS2002_DEVID, STTS2002_DEVID_MASK }, @@ -443,6 +449,8 @@ static int jc42_detect(struct i2c_client *client, struct i2c_board_info *info) } static const struct hwmon_channel_info *jc42_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_MAX_HYST | diff --git a/drivers/hwmon/lan966x-hwmon.c b/drivers/hwmon/lan966x-hwmon.c new file mode 100644 index 000000000000..f41df053ac31 --- /dev/null +++ b/drivers/hwmon/lan966x-hwmon.c @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The original translation formulae of the temperature (in degrees of Celsius) + * are as follows: + * + * T = -3.4627e-11*(N^4) + 1.1023e-7*(N^3) + -1.9165e-4*(N^2) + + * 3.0604e-1*(N^1) + -5.6197e1 + * + * where [-56.197, 136.402]C and N = [0, 1023]. + * + * They must be accordingly altered to be suitable for the integer arithmetics. + * The technique is called 'factor redistribution', which just makes sure the + * multiplications and divisions are made so to have a result of the operations + * within the integer numbers limit. In addition we need to translate the + * formulae to accept millidegrees of Celsius. Here what it looks like after + * the alterations: + * + * T = -34627e-12*(N^4) + 110230e-9*(N^3) + -191650e-6*(N^2) + + * 306040e-3*(N^1) + -56197 + * + * where T = [-56197, 136402]mC and N = [0, 1023]. + */ + +static const struct polynomial poly_N_to_temp = { + .terms = { + {4, -34627, 1000, 1}, + {3, 110230, 1000, 1}, + {2, -191650, 1000, 1}, + {1, 306040, 1000, 1}, + {0, -56197, 1, 1} + } +}; + +#define PVT_SENSOR_CTRL 0x0 /* unused */ +#define PVT_SENSOR_CFG 0x4 +#define SENSOR_CFG_CLK_CFG GENMASK(27, 20) +#define SENSOR_CFG_TRIM_VAL GENMASK(13, 9) +#define SENSOR_CFG_SAMPLE_ENA BIT(8) +#define SENSOR_CFG_START_CAPTURE BIT(7) +#define SENSOR_CFG_CONTINIOUS_MODE BIT(6) +#define SENSOR_CFG_PSAMPLE_ENA GENMASK(1, 0) +#define PVT_SENSOR_STAT 0x8 +#define SENSOR_STAT_DATA_VALID BIT(10) +#define SENSOR_STAT_DATA GENMASK(9, 0) + +#define FAN_CFG 0x0 +#define FAN_CFG_DUTY_CYCLE GENMASK(23, 16) +#define INV_POL BIT(3) +#define GATE_ENA BIT(2) +#define PWM_OPEN_COL_ENA BIT(1) +#define FAN_STAT_CFG BIT(0) +#define FAN_PWM_FREQ 0x4 +#define FAN_PWM_CYC_10US GENMASK(25, 15) +#define FAN_PWM_FREQ_FREQ GENMASK(14, 0) +#define FAN_CNT 0xc +#define FAN_CNT_DATA GENMASK(15, 0) + +#define LAN966X_PVT_CLK 1200000 /* 1.2 MHz */ + +struct lan966x_hwmon { + struct regmap *regmap_pvt; + struct regmap *regmap_fan; + struct clk *clk; + unsigned long clk_rate; +}; + +static int lan966x_hwmon_read_temp(struct device *dev, long *val) +{ + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); + unsigned int data; + int ret; + + ret = regmap_read(hwmon->regmap_pvt, PVT_SENSOR_STAT, &data); + if (ret < 0) + return ret; + + if (!(data & SENSOR_STAT_DATA_VALID)) + return -ENODATA; + + *val = polynomial_calc(&poly_N_to_temp, + FIELD_GET(SENSOR_STAT_DATA, data)); + + return 0; +} + +static int lan966x_hwmon_read_fan(struct device *dev, long *val) +{ + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); + unsigned int data; + int ret; + + ret = regmap_read(hwmon->regmap_fan, FAN_CNT, &data); + if (ret < 0) + return ret; + + /* + * Data is given in pulses per second. Assume two pulses + * per revolution. + */ + *val = FIELD_GET(FAN_CNT_DATA, data) * 60 / 2; + + return 0; +} + +static int lan966x_hwmon_read_pwm(struct device *dev, long *val) +{ + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); + unsigned int data; + int ret; + + ret = regmap_read(hwmon->regmap_fan, FAN_CFG, &data); + if (ret < 0) + return ret; + + *val = FIELD_GET(FAN_CFG_DUTY_CYCLE, data); + + return 0; +} + +static int lan966x_hwmon_read_pwm_freq(struct device *dev, long *val) +{ + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); + unsigned long tmp; + unsigned int data; + int ret; + + ret = regmap_read(hwmon->regmap_fan, FAN_PWM_FREQ, &data); + if (ret < 0) + return ret; + + /* + * Datasheet says it is sys_clk / 256 / pwm_freq. But in reality + * it is sys_clk / 256 / (pwm_freq + 1). + */ + data = FIELD_GET(FAN_PWM_FREQ_FREQ, data) + 1; + tmp = DIV_ROUND_CLOSEST(hwmon->clk_rate, 256); + *val = DIV_ROUND_CLOSEST(tmp, data); + + return 0; +} + +static int lan966x_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_temp: + return lan966x_hwmon_read_temp(dev, val); + case hwmon_fan: + return lan966x_hwmon_read_fan(dev, val); + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + return lan966x_hwmon_read_pwm(dev, val); + case hwmon_pwm_freq: + return lan966x_hwmon_read_pwm_freq(dev, val); + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int lan966x_hwmon_write_pwm(struct device *dev, long val) +{ + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); + + if (val < 0 || val > 255) + return -EINVAL; + + return regmap_update_bits(hwmon->regmap_fan, FAN_CFG, + FAN_CFG_DUTY_CYCLE, + FIELD_PREP(FAN_CFG_DUTY_CYCLE, val)); +} + +static int lan966x_hwmon_write_pwm_freq(struct device *dev, long val) +{ + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); + + if (val <= 0) + return -EINVAL; + + val = DIV_ROUND_CLOSEST(hwmon->clk_rate, val); + val = DIV_ROUND_CLOSEST(val, 256) - 1; + val = clamp_val(val, 0, FAN_PWM_FREQ_FREQ); + + return regmap_update_bits(hwmon->regmap_fan, FAN_PWM_FREQ, + FAN_PWM_FREQ_FREQ, + FIELD_PREP(FAN_PWM_FREQ_FREQ, val)); +} + +static int lan966x_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + return lan966x_hwmon_write_pwm(dev, val); + case hwmon_pwm_freq: + return lan966x_hwmon_write_pwm_freq(dev, val); + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static umode_t lan966x_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + umode_t mode = 0; + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + mode = 0444; + break; + default: + break; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + mode = 0444; + break; + default: + break; + } + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + case hwmon_pwm_freq: + mode = 0644; + break; + default: + break; + } + break; + default: + break; + } + + return mode; +} + +static const struct hwmon_channel_info *lan966x_hwmon_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_FREQ), + NULL +}; + +static const struct hwmon_ops lan966x_hwmon_ops = { + .is_visible = lan966x_hwmon_is_visible, + .read = lan966x_hwmon_read, + .write = lan966x_hwmon_write, +}; + +static const struct hwmon_chip_info lan966x_hwmon_chip_info = { + .ops = &lan966x_hwmon_ops, + .info = lan966x_hwmon_info, +}; + +static void lan966x_hwmon_disable(void *data) +{ + struct lan966x_hwmon *hwmon = data; + + regmap_update_bits(hwmon->regmap_pvt, PVT_SENSOR_CFG, + SENSOR_CFG_SAMPLE_ENA | SENSOR_CFG_CONTINIOUS_MODE, + 0); +} + +static int lan966x_hwmon_enable(struct device *dev, + struct lan966x_hwmon *hwmon) +{ + unsigned int mask = SENSOR_CFG_CLK_CFG | + SENSOR_CFG_SAMPLE_ENA | + SENSOR_CFG_START_CAPTURE | + SENSOR_CFG_CONTINIOUS_MODE | + SENSOR_CFG_PSAMPLE_ENA; + unsigned int val; + unsigned int div; + int ret; + + /* enable continuous mode */ + val = SENSOR_CFG_SAMPLE_ENA | SENSOR_CFG_CONTINIOUS_MODE; + + /* set PVT clock to be between 1.15 and 1.25 MHz */ + div = DIV_ROUND_CLOSEST(hwmon->clk_rate, LAN966X_PVT_CLK); + val |= FIELD_PREP(SENSOR_CFG_CLK_CFG, div); + + ret = regmap_update_bits(hwmon->regmap_pvt, PVT_SENSOR_CFG, + mask, val); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, lan966x_hwmon_disable, hwmon); +} + +static struct regmap *lan966x_init_regmap(struct platform_device *pdev, + const char *name) +{ + struct regmap_config regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + }; + void __iomem *base; + + base = devm_platform_ioremap_resource_byname(pdev, name); + if (IS_ERR(base)) + return ERR_CAST(base); + + regmap_config.name = name; + + return devm_regmap_init_mmio(&pdev->dev, base, ®map_config); +} + +static void lan966x_clk_disable(void *data) +{ + struct lan966x_hwmon *hwmon = data; + + clk_disable_unprepare(hwmon->clk); +} + +static int lan966x_clk_enable(struct device *dev, struct lan966x_hwmon *hwmon) +{ + int ret; + + ret = clk_prepare_enable(hwmon->clk); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, lan966x_clk_disable, hwmon); +} + +static int lan966x_hwmon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct lan966x_hwmon *hwmon; + struct device *hwmon_dev; + int ret; + + hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL); + if (!hwmon) + return -ENOMEM; + + hwmon->clk = devm_clk_get(dev, NULL); + if (IS_ERR(hwmon->clk)) + return dev_err_probe(dev, PTR_ERR(hwmon->clk), + "failed to get clock\n"); + + ret = lan966x_clk_enable(dev, hwmon); + if (ret) + return dev_err_probe(dev, ret, "failed to enable clock\n"); + + hwmon->clk_rate = clk_get_rate(hwmon->clk); + + hwmon->regmap_pvt = lan966x_init_regmap(pdev, "pvt"); + if (IS_ERR(hwmon->regmap_pvt)) + return dev_err_probe(dev, PTR_ERR(hwmon->regmap_pvt), + "failed to get regmap for PVT registers\n"); + + hwmon->regmap_fan = lan966x_init_regmap(pdev, "fan"); + if (IS_ERR(hwmon->regmap_fan)) + return dev_err_probe(dev, PTR_ERR(hwmon->regmap_fan), + "failed to get regmap for fan registers\n"); + + ret = lan966x_hwmon_enable(dev, hwmon); + if (ret) + return dev_err_probe(dev, ret, "failed to enable sensor\n"); + + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, + "lan966x_hwmon", hwmon, + &lan966x_hwmon_chip_info, NULL); + if (IS_ERR(hwmon_dev)) + return dev_err_probe(dev, PTR_ERR(hwmon_dev), + "failed to register hwmon device\n"); + + return 0; +} + +static const struct of_device_id lan966x_hwmon_of_match[] = { + { .compatible = "microchip,lan9668-hwmon" }, + {} +}; +MODULE_DEVICE_TABLE(of, lan966x_hwmon_of_match); + +static struct platform_driver lan966x_hwmon_driver = { + .probe = lan966x_hwmon_probe, + .driver = { + .name = "lan966x-hwmon", + .of_match_table = lan966x_hwmon_of_match, + }, +}; +module_platform_driver(lan966x_hwmon_driver); + +MODULE_DESCRIPTION("LAN966x Hardware Monitoring Driver"); +MODULE_AUTHOR("Michael Walle "); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index afdbb63237b9..66dc826f7962 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -26,6 +26,7 @@ enum lm75_type { /* keep sorted in alphabetical order */ adt75, + at30ts74, ds1775, ds75, ds7505, @@ -128,6 +129,14 @@ static const struct lm75_params device_params[] = { .default_resolution = 12, .default_sample_time = MSEC_PER_SEC / 10, }, + [at30ts74] = { + .set_mask = 3 << 5, /* 12-bit mode*/ + .default_resolution = 12, + .default_sample_time = 200, + .num_sample_times = 4, + .sample_times = (unsigned int []){ 25, 50, 100, 200 }, + .resolutions = (u8 []) {9, 10, 11, 12 }, + }, [ds1775] = { .clr_mask = 3 << 5, .set_mask = 2 << 5, /* 11-bit mode */ @@ -645,6 +654,7 @@ static int lm75_probe(struct i2c_client *client) static const struct i2c_device_id lm75_ids[] = { { "adt75", adt75, }, + { "at30ts74", at30ts74, }, { "ds1775", ds1775, }, { "ds75", ds75, }, { "ds7505", ds7505, }, @@ -680,6 +690,10 @@ static const struct of_device_id __maybe_unused lm75_of_match[] = { .compatible = "adi,adt75", .data = (void *)adt75 }, + { + .compatible = "atmel,at30ts74", + .data = (void *)at30ts74 + }, { .compatible = "dallas,ds1775", .data = (void *)ds1775 diff --git a/drivers/hwmon/lm83.c b/drivers/hwmon/lm83.c index 12370dcefa6a..905f5689f907 100644 --- a/drivers/hwmon/lm83.c +++ b/drivers/hwmon/lm83.c @@ -24,10 +24,8 @@ #include #include #include -#include #include #include -#include /* * Addresses to scan diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index 1c9493c70813..3820f0e61510 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -1707,6 +1707,7 @@ static void lm90_restore_conf(void *_data) static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) { + struct device_node *np = client->dev.of_node; int config, convrate; convrate = lm90_read_reg(client, LM90_REG_R_CONVRATE); @@ -1727,6 +1728,9 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) /* Check Temperature Range Select */ if (data->flags & LM90_HAVE_EXTENDED_TEMP) { + if (of_property_read_bool(np, "ti,extended-range-enable")) + config |= 0x04; + if (config & 0x04) data->flags |= LM90_FLAG_ADT7461_EXT; } diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c index 7352d2b3c756..72489d5d7eaf 100644 --- a/drivers/hwmon/ltc2992.c +++ b/drivers/hwmon/ltc2992.c @@ -811,68 +811,32 @@ static const struct hwmon_ops ltc2992_hwmon_ops = { .write = ltc2992_write, }; -static const u32 ltc2992_chip_config[] = { - HWMON_C_IN_RESET_HISTORY, - 0 -}; - -static const struct hwmon_channel_info ltc2992_chip = { - .type = hwmon_chip, - .config = ltc2992_chip_config, -}; - -static const u32 ltc2992_in_config[] = { - HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | - HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, - HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | - HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, - HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | - HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, - HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | - HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, - HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | - HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, - HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | - HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, - 0 -}; - -static const struct hwmon_channel_info ltc2992_in = { - .type = hwmon_in, - .config = ltc2992_in_config, -}; - -static const u32 ltc2992_curr_config[] = { - HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | HWMON_C_MAX | - HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM, - HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | HWMON_C_MAX | - HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM, - 0 -}; - -static const struct hwmon_channel_info ltc2992_curr = { - .type = hwmon_curr, - .config = ltc2992_curr_config, -}; - -static const u32 ltc2992_power_config[] = { - HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MIN | HWMON_P_MAX | - HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM, - HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MIN | HWMON_P_MAX | - HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM, - 0 -}; - -static const struct hwmon_channel_info ltc2992_power = { - .type = hwmon_power, - .config = ltc2992_power_config, -}; - static const struct hwmon_channel_info *ltc2992_info[] = { - <c2992_chip, - <c2992_in, - <c2992_curr, - <c2992_power, + HWMON_CHANNEL_INFO(chip, + HWMON_C_IN_RESET_HISTORY), + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | + HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | + HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | + HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | + HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | + HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | + HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM), + HWMON_CHANNEL_INFO(curr, + HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | + HWMON_C_MAX | HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM, + HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | + HWMON_C_MAX | HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM), + HWMON_CHANNEL_INFO(power, + HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | + HWMON_P_MIN | HWMON_P_MAX | HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM, + HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | + HWMON_P_MIN | HWMON_P_MAX | HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM), NULL }; diff --git a/drivers/hwmon/mr75203.c b/drivers/hwmon/mr75203.c index 1ba1e3145969..26278b0f17a9 100644 --- a/drivers/hwmon/mr75203.c +++ b/drivers/hwmon/mr75203.c @@ -223,16 +223,6 @@ static int pvt_read(struct device *dev, enum hwmon_sensor_types type, } } -static const u32 pvt_chip_config[] = { - HWMON_C_REGISTER_TZ, - 0 -}; - -static const struct hwmon_channel_info pvt_chip = { - .type = hwmon_chip, - .config = pvt_chip_config, -}; - static struct hwmon_channel_info pvt_temp = { .type = hwmon_temp, }; @@ -555,7 +545,7 @@ static int mr75203_probe(struct platform_device *pdev) pvt_info = devm_kcalloc(dev, val + 2, sizeof(*pvt_info), GFP_KERNEL); if (!pvt_info) return -ENOMEM; - pvt_info[0] = &pvt_chip; + pvt_info[0] = HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ); index = 1; if (ts_num) { diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775-core.c similarity index 66% rename from drivers/hwmon/nct6775.c rename to drivers/hwmon/nct6775-core.c index 2b91f7e05126..446964cbae4c 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775-core.c @@ -44,25 +44,21 @@ #include #include #include -#include #include #include -#include #include #include -#include #include -#include -#include #include -#include +#include #include "lm75.h" +#include "nct6775.h" + +#undef DEFAULT_SYMBOL_NAMESPACE +#define DEFAULT_SYMBOL_NAMESPACE HWMON_NCT6775 #define USE_ALTERNATE -enum kinds { nct6106, nct6116, nct6775, nct6776, nct6779, nct6791, nct6792, - nct6793, nct6795, nct6796, nct6797, nct6798 }; - /* used to set data->name = nct6775_device_names[data->sio_kind] */ static const char * const nct6775_device_names[] = { "nct6106", @@ -79,242 +75,6 @@ static const char * const nct6775_device_names[] = { "nct6798", }; -static const char * const nct6775_sio_names[] __initconst = { - "NCT6106D", - "NCT6116D", - "NCT6775F", - "NCT6776D/F", - "NCT6779D", - "NCT6791D", - "NCT6792D", - "NCT6793D", - "NCT6795D", - "NCT6796D", - "NCT6797D", - "NCT6798D", -}; - -static unsigned short force_id; -module_param(force_id, ushort, 0); -MODULE_PARM_DESC(force_id, "Override the detected device ID"); - -static unsigned short fan_debounce; -module_param(fan_debounce, ushort, 0); -MODULE_PARM_DESC(fan_debounce, "Enable debouncing for fan RPM signal"); - -#define DRVNAME "nct6775" - -/* - * Super-I/O constants and functions - */ - -#define NCT6775_LD_ACPI 0x0a -#define NCT6775_LD_HWM 0x0b -#define NCT6775_LD_VID 0x0d -#define NCT6775_LD_12 0x12 - -#define SIO_REG_LDSEL 0x07 /* Logical device select */ -#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ -#define SIO_REG_ENABLE 0x30 /* Logical device enable */ -#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ - -#define SIO_NCT6106_ID 0xc450 -#define SIO_NCT6116_ID 0xd280 -#define SIO_NCT6775_ID 0xb470 -#define SIO_NCT6776_ID 0xc330 -#define SIO_NCT6779_ID 0xc560 -#define SIO_NCT6791_ID 0xc800 -#define SIO_NCT6792_ID 0xc910 -#define SIO_NCT6793_ID 0xd120 -#define SIO_NCT6795_ID 0xd350 -#define SIO_NCT6796_ID 0xd420 -#define SIO_NCT6797_ID 0xd450 -#define SIO_NCT6798_ID 0xd428 -#define SIO_ID_MASK 0xFFF8 - -enum pwm_enable { off, manual, thermal_cruise, speed_cruise, sf3, sf4 }; -enum sensor_access { access_direct, access_asuswmi }; - -struct nct6775_sio_data { - int sioreg; - int ld; - enum kinds kind; - enum sensor_access access; - - /* superio_() callbacks */ - void (*sio_outb)(struct nct6775_sio_data *sio_data, int reg, int val); - int (*sio_inb)(struct nct6775_sio_data *sio_data, int reg); - void (*sio_select)(struct nct6775_sio_data *sio_data, int ld); - int (*sio_enter)(struct nct6775_sio_data *sio_data); - void (*sio_exit)(struct nct6775_sio_data *sio_data); -}; - -#define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66" -#define ASUSWMI_METHODID_RSIO 0x5253494F -#define ASUSWMI_METHODID_WSIO 0x5753494F -#define ASUSWMI_METHODID_RHWM 0x5248574D -#define ASUSWMI_METHODID_WHWM 0x5748574D -#define ASUSWMI_UNSUPPORTED_METHOD 0xFFFFFFFE - -static int nct6775_asuswmi_evaluate_method(u32 method_id, u8 bank, u8 reg, u8 val, u32 *retval) -{ -#if IS_ENABLED(CONFIG_ACPI_WMI) - u32 args = bank | (reg << 8) | (val << 16); - struct acpi_buffer input = { (acpi_size) sizeof(args), &args }; - struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; - acpi_status status; - union acpi_object *obj; - u32 tmp = ASUSWMI_UNSUPPORTED_METHOD; - - status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, 0, - method_id, &input, &output); - - if (ACPI_FAILURE(status)) - return -EIO; - - obj = output.pointer; - if (obj && obj->type == ACPI_TYPE_INTEGER) - tmp = obj->integer.value; - - if (retval) - *retval = tmp; - - kfree(obj); - - if (tmp == ASUSWMI_UNSUPPORTED_METHOD) - return -ENODEV; - return 0; -#else - return -EOPNOTSUPP; -#endif -} - -static inline int nct6775_asuswmi_write(u8 bank, u8 reg, u8 val) -{ - return nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_WHWM, bank, - reg, val, NULL); -} - -static inline int nct6775_asuswmi_read(u8 bank, u8 reg, u8 *val) -{ - u32 ret, tmp = 0; - - ret = nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_RHWM, bank, - reg, 0, &tmp); - *val = tmp; - return ret; -} - -static int superio_wmi_inb(struct nct6775_sio_data *sio_data, int reg) -{ - int tmp = 0; - - nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_RSIO, sio_data->ld, - reg, 0, &tmp); - return tmp; -} - -static void superio_wmi_outb(struct nct6775_sio_data *sio_data, int reg, int val) -{ - nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_WSIO, sio_data->ld, - reg, val, NULL); -} - -static void superio_wmi_select(struct nct6775_sio_data *sio_data, int ld) -{ - sio_data->ld = ld; -} - -static int superio_wmi_enter(struct nct6775_sio_data *sio_data) -{ - return 0; -} - -static void superio_wmi_exit(struct nct6775_sio_data *sio_data) -{ -} - -static void superio_outb(struct nct6775_sio_data *sio_data, int reg, int val) -{ - int ioreg = sio_data->sioreg; - - outb(reg, ioreg); - outb(val, ioreg + 1); -} - -static int superio_inb(struct nct6775_sio_data *sio_data, int reg) -{ - int ioreg = sio_data->sioreg; - - outb(reg, ioreg); - return inb(ioreg + 1); -} - -static void superio_select(struct nct6775_sio_data *sio_data, int ld) -{ - int ioreg = sio_data->sioreg; - - outb(SIO_REG_LDSEL, ioreg); - outb(ld, ioreg + 1); -} - -static int superio_enter(struct nct6775_sio_data *sio_data) -{ - int ioreg = sio_data->sioreg; - - /* - * Try to reserve and for exclusive access. - */ - if (!request_muxed_region(ioreg, 2, DRVNAME)) - return -EBUSY; - - outb(0x87, ioreg); - outb(0x87, ioreg); - - return 0; -} - -static void superio_exit(struct nct6775_sio_data *sio_data) -{ - int ioreg = sio_data->sioreg; - - outb(0xaa, ioreg); - outb(0x02, ioreg); - outb(0x02, ioreg + 1); - release_region(ioreg, 2); -} - -/* - * ISA constants - */ - -#define IOREGION_ALIGNMENT (~7) -#define IOREGION_OFFSET 5 -#define IOREGION_LENGTH 2 -#define ADDR_REG_OFFSET 0 -#define DATA_REG_OFFSET 1 - -#define NCT6775_REG_BANK 0x4E -#define NCT6775_REG_CONFIG 0x40 -#define NCT6775_PORT_CHIPID 0x58 - -/* - * Not currently used: - * REG_MAN_ID has the value 0x5ca3 for all supported chips. - * REG_CHIP_ID == 0x88/0xa1/0xc1 depending on chip model. - * REG_MAN_ID is at port 0x4f - * REG_CHIP_ID is at port 0x58 - */ - -#define NUM_TEMP 10 /* Max number of temp attribute sets w/ limits*/ -#define NUM_TEMP_FIXED 6 /* Max number of fixed temp attribute sets */ -#define NUM_TSI_TEMP 8 /* Max number of TSI temp register pairs */ - -#define NUM_REG_ALARM 7 /* Max number of alarm registers */ -#define NUM_REG_BEEP 5 /* Max number of beep registers */ - -#define NUM_FAN 7 - /* Common and NCT6775 specific data */ /* Voltage min/max registers for nr=7..14 are in bank 5 */ @@ -333,11 +93,6 @@ static const u16 NCT6775_REG_IN[] = { #define NCT6775_REG_DIODE 0x5E #define NCT6775_DIODE_MASK 0x02 -#define NCT6775_REG_FANDIV1 0x506 -#define NCT6775_REG_FANDIV2 0x507 - -#define NCT6775_REG_CR_FAN_DEBOUNCE 0xf0 - static const u16 NCT6775_REG_ALARM[NUM_REG_ALARM] = { 0x459, 0x45A, 0x45B }; /* 0..15 voltages, 16..23 fans, 24..29 temperatures, 30..31 intrusion */ @@ -351,10 +106,6 @@ static const s8 NCT6775_ALARM_BITS[] = { 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ 12, -1 }; /* intrusion0, intrusion1 */ -#define FAN_ALARM_BASE 16 -#define TEMP_ALARM_BASE 24 -#define INTRUSION_ALARM_BASE 30 - static const u16 NCT6775_REG_BEEP[NUM_REG_BEEP] = { 0x56, 0x57, 0x453, 0x4e }; /* @@ -370,11 +121,6 @@ static const s8 NCT6775_BEEP_BITS[] = { 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ 12, -1 }; /* intrusion0, intrusion1 */ -#define BEEP_ENABLE_BASE 15 - -static const u8 NCT6775_REG_CR_CASEOPEN_CLR[] = { 0xe6, 0xee }; -static const u8 NCT6775_CR_CASEOPEN_CLR_MASK[] = { 0x20, 0x01 }; - /* DC or PWM output fan configuration */ static const u8 NCT6775_REG_PWM_MODE[] = { 0x04, 0x04, 0x12 }; static const u8 NCT6775_PWM_MODE_MASK[] = { 0x01, 0x02, 0x01 }; @@ -690,8 +436,6 @@ static const u16 NCT6779_REG_TEMP_CRIT[32] = { /* NCT6791 specific data */ -#define NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE 0x28 - static const u16 NCT6791_REG_WEIGHT_TEMP_SEL[NUM_FAN] = { 0, 0x239 }; static const u16 NCT6791_REG_WEIGHT_TEMP_STEP[NUM_FAN] = { 0, 0x23a }; static const u16 NCT6791_REG_WEIGHT_TEMP_STEP_TOL[NUM_FAN] = { 0, 0x23b }; @@ -1191,165 +935,6 @@ static inline unsigned int tsi_temp_from_reg(unsigned int reg) * Data structures and manipulation thereof */ -struct nct6775_data { - int addr; /* IO base of hw monitor block */ - struct nct6775_sio_data *sio_data; - enum kinds kind; - const char *name; - - const struct attribute_group *groups[7]; - - u16 reg_temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst, - * 3=temp_crit, 4=temp_lcrit - */ - u8 temp_src[NUM_TEMP]; - u16 reg_temp_config[NUM_TEMP]; - const char * const *temp_label; - u32 temp_mask; - u32 virt_temp_mask; - - u16 REG_CONFIG; - u16 REG_VBAT; - u16 REG_DIODE; - u8 DIODE_MASK; - - const s8 *ALARM_BITS; - const s8 *BEEP_BITS; - - const u16 *REG_VIN; - const u16 *REG_IN_MINMAX[2]; - - const u16 *REG_TARGET; - const u16 *REG_FAN; - const u16 *REG_FAN_MODE; - const u16 *REG_FAN_MIN; - const u16 *REG_FAN_PULSES; - const u16 *FAN_PULSE_SHIFT; - const u16 *REG_FAN_TIME[3]; - - const u16 *REG_TOLERANCE_H; - - const u8 *REG_PWM_MODE; - const u8 *PWM_MODE_MASK; - - const u16 *REG_PWM[7]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor, - * [3]=pwm_max, [4]=pwm_step, - * [5]=weight_duty_step, [6]=weight_duty_base - */ - const u16 *REG_PWM_READ; - - const u16 *REG_CRITICAL_PWM_ENABLE; - u8 CRITICAL_PWM_ENABLE_MASK; - const u16 *REG_CRITICAL_PWM; - - const u16 *REG_AUTO_TEMP; - const u16 *REG_AUTO_PWM; - - const u16 *REG_CRITICAL_TEMP; - const u16 *REG_CRITICAL_TEMP_TOLERANCE; - - const u16 *REG_TEMP_SOURCE; /* temp register sources */ - const u16 *REG_TEMP_SEL; - const u16 *REG_WEIGHT_TEMP_SEL; - const u16 *REG_WEIGHT_TEMP[3]; /* 0=base, 1=tolerance, 2=step */ - - const u16 *REG_TEMP_OFFSET; - - const u16 *REG_ALARM; - const u16 *REG_BEEP; - - const u16 *REG_TSI_TEMP; - - unsigned int (*fan_from_reg)(u16 reg, unsigned int divreg); - unsigned int (*fan_from_reg_min)(u16 reg, unsigned int divreg); - - struct mutex update_lock; - bool valid; /* true if following fields are valid */ - unsigned long last_updated; /* In jiffies */ - - /* Register values */ - u8 bank; /* current register bank */ - u8 in_num; /* number of in inputs we have */ - u8 in[15][3]; /* [0]=in, [1]=in_max, [2]=in_min */ - unsigned int rpm[NUM_FAN]; - u16 fan_min[NUM_FAN]; - u8 fan_pulses[NUM_FAN]; - u8 fan_div[NUM_FAN]; - u8 has_pwm; - u8 has_fan; /* some fan inputs can be disabled */ - u8 has_fan_min; /* some fans don't have min register */ - bool has_fan_div; - - u8 num_temp_alarms; /* 2, 3, or 6 */ - u8 num_temp_beeps; /* 2, 3, or 6 */ - u8 temp_fixed_num; /* 3 or 6 */ - u8 temp_type[NUM_TEMP_FIXED]; - s8 temp_offset[NUM_TEMP_FIXED]; - s16 temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst, - * 3=temp_crit, 4=temp_lcrit */ - s16 tsi_temp[NUM_TSI_TEMP]; - u64 alarms; - u64 beeps; - - u8 pwm_num; /* number of pwm */ - u8 pwm_mode[NUM_FAN]; /* 0->DC variable voltage, - * 1->PWM variable duty cycle - */ - enum pwm_enable pwm_enable[NUM_FAN]; - /* 0->off - * 1->manual - * 2->thermal cruise mode (also called SmartFan I) - * 3->fan speed cruise mode - * 4->SmartFan III - * 5->enhanced variable thermal cruise (SmartFan IV) - */ - u8 pwm[7][NUM_FAN]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor, - * [3]=pwm_max, [4]=pwm_step, - * [5]=weight_duty_step, [6]=weight_duty_base - */ - - u8 target_temp[NUM_FAN]; - u8 target_temp_mask; - u32 target_speed[NUM_FAN]; - u32 target_speed_tolerance[NUM_FAN]; - u8 speed_tolerance_limit; - - u8 temp_tolerance[2][NUM_FAN]; - u8 tolerance_mask; - - u8 fan_time[3][NUM_FAN]; /* 0 = stop_time, 1 = step_up, 2 = step_down */ - - /* Automatic fan speed control registers */ - int auto_pwm_num; - u8 auto_pwm[NUM_FAN][7]; - u8 auto_temp[NUM_FAN][7]; - u8 pwm_temp_sel[NUM_FAN]; - u8 pwm_weight_temp_sel[NUM_FAN]; - u8 weight_temp[3][NUM_FAN]; /* 0->temp_step, 1->temp_step_tol, - * 2->temp_base - */ - - u8 vid; - u8 vrm; - - bool have_vid; - - u16 have_temp; - u16 have_temp_fixed; - u16 have_tsi_temp; - u16 have_in; - - /* Remember extra register values over suspend/resume */ - u8 vbat; - u8 fandiv1; - u8 fandiv2; - u8 sio_reg_enable; - - /* nct6775_*() callbacks */ - u16 (*read_value)(struct nct6775_data *data, u16 reg); - int (*write_value)(struct nct6775_data *data, u16 reg, u16 value); -}; - struct sensor_device_template { struct device_attribute dev_attr; union { @@ -1405,10 +990,8 @@ struct sensor_template_group { int base; }; -static struct attribute_group * -nct6775_create_attr_group(struct device *dev, - const struct sensor_template_group *tg, - int repeat) +static int nct6775_add_template_attr_group(struct device *dev, struct nct6775_data *data, + const struct sensor_template_group *tg, int repeat) { struct attribute_group *group; struct sensor_device_attr_u *su; @@ -1419,28 +1002,28 @@ nct6775_create_attr_group(struct device *dev, int i, count; if (repeat <= 0) - return ERR_PTR(-EINVAL); + return -EINVAL; t = tg->templates; for (count = 0; *t; t++, count++) ; if (count == 0) - return ERR_PTR(-EINVAL); + return -EINVAL; group = devm_kzalloc(dev, sizeof(*group), GFP_KERNEL); if (group == NULL) - return ERR_PTR(-ENOMEM); + return -ENOMEM; attrs = devm_kcalloc(dev, repeat * count + 1, sizeof(*attrs), GFP_KERNEL); if (attrs == NULL) - return ERR_PTR(-ENOMEM); + return -ENOMEM; su = devm_kzalloc(dev, array3_size(repeat, count, sizeof(*su)), GFP_KERNEL); if (su == NULL) - return ERR_PTR(-ENOMEM); + return -ENOMEM; group->attrs = attrs; group->is_visible = tg->is_visible; @@ -1478,10 +1061,10 @@ nct6775_create_attr_group(struct device *dev, } } - return group; + return nct6775_add_attr_group(data, group); } -static bool is_word_sized(struct nct6775_data *data, u16 reg) +bool nct6775_reg_is_word_sized(struct nct6775_data *data, u16 reg) { switch (data->kind) { case nct6106: @@ -1538,180 +1121,81 @@ static bool is_word_sized(struct nct6775_data *data, u16 reg) } return false; } +EXPORT_SYMBOL_GPL(nct6775_reg_is_word_sized); -static inline void nct6775_wmi_set_bank(struct nct6775_data *data, u16 reg) +/* We left-align 8-bit temperature values to make the code simpler */ +static int nct6775_read_temp(struct nct6775_data *data, u16 reg, u16 *val) { - u8 bank = reg >> 8; + int err; - data->bank = bank; -} - -static u16 nct6775_wmi_read_value(struct nct6775_data *data, u16 reg) -{ - int res, err, word_sized = is_word_sized(data, reg); - u8 tmp = 0; - - nct6775_wmi_set_bank(data, reg); - - err = nct6775_asuswmi_read(data->bank, reg & 0xff, &tmp); + err = nct6775_read_value(data, reg, val); if (err) - return 0; + return err; - res = tmp; - if (word_sized) { - err = nct6775_asuswmi_read(data->bank, (reg & 0xff) + 1, &tmp); - if (err) - return 0; + if (!nct6775_reg_is_word_sized(data, reg)) + *val <<= 8; - res = (res << 8) + tmp; - } - return res; -} - -static int nct6775_wmi_write_value(struct nct6775_data *data, u16 reg, u16 value) -{ - int res, word_sized = is_word_sized(data, reg); - - nct6775_wmi_set_bank(data, reg); - - if (word_sized) { - res = nct6775_asuswmi_write(data->bank, reg & 0xff, value >> 8); - if (res) - return res; - - res = nct6775_asuswmi_write(data->bank, (reg & 0xff) + 1, value); - } else { - res = nct6775_asuswmi_write(data->bank, reg & 0xff, value); - } - - return res; -} - -/* - * On older chips, only registers 0x50-0x5f are banked. - * On more recent chips, all registers are banked. - * Assume that is the case and set the bank number for each access. - * Cache the bank number so it only needs to be set if it changes. - */ -static inline void nct6775_set_bank(struct nct6775_data *data, u16 reg) -{ - u8 bank = reg >> 8; - - if (data->bank != bank) { - outb_p(NCT6775_REG_BANK, data->addr + ADDR_REG_OFFSET); - outb_p(bank, data->addr + DATA_REG_OFFSET); - data->bank = bank; - } -} - -static u16 nct6775_read_value(struct nct6775_data *data, u16 reg) -{ - int res, word_sized = is_word_sized(data, reg); - - nct6775_set_bank(data, reg); - outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); - res = inb_p(data->addr + DATA_REG_OFFSET); - if (word_sized) { - outb_p((reg & 0xff) + 1, - data->addr + ADDR_REG_OFFSET); - res = (res << 8) + inb_p(data->addr + DATA_REG_OFFSET); - } - return res; -} - -static int nct6775_write_value(struct nct6775_data *data, u16 reg, u16 value) -{ - int word_sized = is_word_sized(data, reg); - - nct6775_set_bank(data, reg); - outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); - if (word_sized) { - outb_p(value >> 8, data->addr + DATA_REG_OFFSET); - outb_p((reg & 0xff) + 1, - data->addr + ADDR_REG_OFFSET); - } - outb_p(value & 0xff, data->addr + DATA_REG_OFFSET); return 0; } -/* We left-align 8-bit temperature values to make the code simpler */ -static u16 nct6775_read_temp(struct nct6775_data *data, u16 reg) -{ - u16 res; - - res = data->read_value(data, reg); - if (!is_word_sized(data, reg)) - res <<= 8; - - return res; -} - -static int nct6775_write_temp(struct nct6775_data *data, u16 reg, u16 value) -{ - if (!is_word_sized(data, reg)) - value >>= 8; - return data->write_value(data, reg, value); -} - /* This function assumes that the caller holds data->update_lock */ -static void nct6775_write_fan_div(struct nct6775_data *data, int nr) +static int nct6775_write_fan_div(struct nct6775_data *data, int nr) { - u8 reg; + u16 reg; + int err; + u16 fandiv_reg = nr < 2 ? NCT6775_REG_FANDIV1 : NCT6775_REG_FANDIV2; + unsigned int oddshift = (nr & 1) * 4; /* masks shift by four if nr is odd */ - switch (nr) { - case 0: - reg = (data->read_value(data, NCT6775_REG_FANDIV1) & 0x70) - | (data->fan_div[0] & 0x7); - data->write_value(data, NCT6775_REG_FANDIV1, reg); - break; - case 1: - reg = (data->read_value(data, NCT6775_REG_FANDIV1) & 0x7) - | ((data->fan_div[1] << 4) & 0x70); - data->write_value(data, NCT6775_REG_FANDIV1, reg); - break; - case 2: - reg = (data->read_value(data, NCT6775_REG_FANDIV2) & 0x70) - | (data->fan_div[2] & 0x7); - data->write_value(data, NCT6775_REG_FANDIV2, reg); - break; - case 3: - reg = (data->read_value(data, NCT6775_REG_FANDIV2) & 0x7) - | ((data->fan_div[3] << 4) & 0x70); - data->write_value(data, NCT6775_REG_FANDIV2, reg); - break; - } + err = nct6775_read_value(data, fandiv_reg, ®); + if (err) + return err; + reg &= 0x70 >> oddshift; + reg |= data->fan_div[nr] & (0x7 << oddshift); + return nct6775_write_value(data, fandiv_reg, reg); } -static void nct6775_write_fan_div_common(struct nct6775_data *data, int nr) +static int nct6775_write_fan_div_common(struct nct6775_data *data, int nr) { if (data->kind == nct6775) - nct6775_write_fan_div(data, nr); + return nct6775_write_fan_div(data, nr); + return 0; } -static void nct6775_update_fan_div(struct nct6775_data *data) +static int nct6775_update_fan_div(struct nct6775_data *data) { - u8 i; + int err; + u16 i; - i = data->read_value(data, NCT6775_REG_FANDIV1); + err = nct6775_read_value(data, NCT6775_REG_FANDIV1, &i); + if (err) + return err; data->fan_div[0] = i & 0x7; data->fan_div[1] = (i & 0x70) >> 4; - i = data->read_value(data, NCT6775_REG_FANDIV2); + err = nct6775_read_value(data, NCT6775_REG_FANDIV2, &i); + if (err) + return err; data->fan_div[2] = i & 0x7; if (data->has_fan & BIT(3)) data->fan_div[3] = (i & 0x70) >> 4; + + return 0; } -static void nct6775_update_fan_div_common(struct nct6775_data *data) +static int nct6775_update_fan_div_common(struct nct6775_data *data) { if (data->kind == nct6775) - nct6775_update_fan_div(data); + return nct6775_update_fan_div(data); + return 0; } -static void nct6775_init_fan_div(struct nct6775_data *data) +static int nct6775_init_fan_div(struct nct6775_data *data) { - int i; + int i, err; + + err = nct6775_update_fan_div_common(data); + if (err) + return err; - nct6775_update_fan_div_common(data); /* * For all fans, start with highest divider value if the divider * register is not initialized. This ensures that we get a @@ -1723,19 +1207,26 @@ static void nct6775_init_fan_div(struct nct6775_data *data) continue; if (data->fan_div[i] == 0) { data->fan_div[i] = 7; - nct6775_write_fan_div_common(data, i); + err = nct6775_write_fan_div_common(data, i); + if (err) + return err; } } + + return 0; } -static void nct6775_init_fan_common(struct device *dev, - struct nct6775_data *data) +static int nct6775_init_fan_common(struct device *dev, + struct nct6775_data *data) { - int i; - u8 reg; + int i, err; + u16 reg; - if (data->has_fan_div) - nct6775_init_fan_div(data); + if (data->has_fan_div) { + err = nct6775_init_fan_div(data); + if (err) + return err; + } /* * If fan_min is not set (0), set it to 0xff to disable it. This @@ -1743,23 +1234,30 @@ static void nct6775_init_fan_common(struct device *dev, */ for (i = 0; i < ARRAY_SIZE(data->fan_min); i++) { if (data->has_fan_min & BIT(i)) { - reg = data->read_value(data, data->REG_FAN_MIN[i]); - if (!reg) - data->write_value(data, data->REG_FAN_MIN[i], - data->has_fan_div ? 0xff - : 0xff1f); + err = nct6775_read_value(data, data->REG_FAN_MIN[i], ®); + if (err) + return err; + if (!reg) { + err = nct6775_write_value(data, data->REG_FAN_MIN[i], + data->has_fan_div ? 0xff : 0xff1f); + if (err) + return err; + } } } + + return 0; } -static void nct6775_select_fan_div(struct device *dev, - struct nct6775_data *data, int nr, u16 reg) +static int nct6775_select_fan_div(struct device *dev, + struct nct6775_data *data, int nr, u16 reg) { + int err; u8 fan_div = data->fan_div[nr]; u16 fan_min; if (!data->has_fan_div) - return; + return 0; /* * If we failed to measure the fan speed, or the reported value is not @@ -1791,36 +1289,46 @@ static void nct6775_select_fan_div(struct device *dev, } if (fan_min != data->fan_min[nr]) { data->fan_min[nr] = fan_min; - data->write_value(data, data->REG_FAN_MIN[nr], - fan_min); + err = nct6775_write_value(data, data->REG_FAN_MIN[nr], fan_min); + if (err) + return err; } } data->fan_div[nr] = fan_div; - nct6775_write_fan_div_common(data, nr); + err = nct6775_write_fan_div_common(data, nr); + if (err) + return err; } + + return 0; } -static void nct6775_update_pwm(struct device *dev) +static int nct6775_update_pwm(struct device *dev) { struct nct6775_data *data = dev_get_drvdata(dev); - int i, j; - int fanmodecfg, reg; + int i, j, err; + u16 fanmodecfg, reg; bool duty_is_dc; for (i = 0; i < data->pwm_num; i++) { if (!(data->has_pwm & BIT(i))) continue; - duty_is_dc = data->REG_PWM_MODE[i] && - (data->read_value(data, data->REG_PWM_MODE[i]) - & data->PWM_MODE_MASK[i]); + err = nct6775_read_value(data, data->REG_PWM_MODE[i], ®); + if (err) + return err; + duty_is_dc = data->REG_PWM_MODE[i] && (reg & data->PWM_MODE_MASK[i]); data->pwm_mode[i] = !duty_is_dc; - fanmodecfg = data->read_value(data, data->REG_FAN_MODE[i]); + err = nct6775_read_value(data, data->REG_FAN_MODE[i], &fanmodecfg); + if (err) + return err; for (j = 0; j < ARRAY_SIZE(data->REG_PWM); j++) { if (data->REG_PWM[j] && data->REG_PWM[j][i]) { - data->pwm[j][i] = data->read_value(data, - data->REG_PWM[j][i]); + err = nct6775_read_value(data, data->REG_PWM[j][i], ®); + if (err) + return err; + data->pwm[j][i] = reg; } } @@ -1835,17 +1343,22 @@ static void nct6775_update_pwm(struct device *dev) u8 t = fanmodecfg & 0x0f; if (data->REG_TOLERANCE_H) { - t |= (data->read_value(data, - data->REG_TOLERANCE_H[i]) & 0x70) >> 1; + err = nct6775_read_value(data, data->REG_TOLERANCE_H[i], ®); + if (err) + return err; + t |= (reg & 0x70) >> 1; } data->target_speed_tolerance[i] = t; } - data->temp_tolerance[1][i] = - data->read_value(data, - data->REG_CRITICAL_TEMP_TOLERANCE[i]); + err = nct6775_read_value(data, data->REG_CRITICAL_TEMP_TOLERANCE[i], ®); + if (err) + return err; + data->temp_tolerance[1][i] = reg; - reg = data->read_value(data, data->REG_TEMP_SEL[i]); + err = nct6775_read_value(data, data->REG_TEMP_SEL[i], ®); + if (err) + return err; data->pwm_temp_sel[i] = reg & 0x1f; /* If fan can stop, report floor as 0 */ if (reg & 0x80) @@ -1854,7 +1367,9 @@ static void nct6775_update_pwm(struct device *dev) if (!data->REG_WEIGHT_TEMP_SEL[i]) continue; - reg = data->read_value(data, data->REG_WEIGHT_TEMP_SEL[i]); + err = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[i], ®); + if (err) + return err; data->pwm_weight_temp_sel[i] = reg & 0x1f; /* If weight is disabled, report weight source as 0 */ if (!(reg & 0x80)) @@ -1862,29 +1377,37 @@ static void nct6775_update_pwm(struct device *dev) /* Weight temp data */ for (j = 0; j < ARRAY_SIZE(data->weight_temp); j++) { - data->weight_temp[j][i] = data->read_value(data, - data->REG_WEIGHT_TEMP[j][i]); + err = nct6775_read_value(data, data->REG_WEIGHT_TEMP[j][i], ®); + if (err) + return err; + data->weight_temp[j][i] = reg; } } + + return 0; } -static void nct6775_update_pwm_limits(struct device *dev) +static int nct6775_update_pwm_limits(struct device *dev) { struct nct6775_data *data = dev_get_drvdata(dev); - int i, j; - u8 reg; - u16 reg_t; + int i, j, err; + u16 reg, reg_t; for (i = 0; i < data->pwm_num; i++) { if (!(data->has_pwm & BIT(i))) continue; for (j = 0; j < ARRAY_SIZE(data->fan_time); j++) { - data->fan_time[j][i] = - data->read_value(data, data->REG_FAN_TIME[j][i]); + err = nct6775_read_value(data, data->REG_FAN_TIME[j][i], ®); + if (err) + return err; + data->fan_time[j][i] = reg; } - reg_t = data->read_value(data, data->REG_TARGET[i]); + err = nct6775_read_value(data, data->REG_TARGET[i], ®_t); + if (err) + return err; + /* Update only in matching mode or if never updated */ if (!data->target_temp[i] || data->pwm_enable[i] == thermal_cruise) @@ -1892,29 +1415,37 @@ static void nct6775_update_pwm_limits(struct device *dev) if (!data->target_speed[i] || data->pwm_enable[i] == speed_cruise) { if (data->REG_TOLERANCE_H) { - reg_t |= (data->read_value(data, - data->REG_TOLERANCE_H[i]) & 0x0f) << 8; + err = nct6775_read_value(data, data->REG_TOLERANCE_H[i], ®); + if (err) + return err; + reg_t |= (reg & 0x0f) << 8; } data->target_speed[i] = reg_t; } for (j = 0; j < data->auto_pwm_num; j++) { - data->auto_pwm[i][j] = - data->read_value(data, - NCT6775_AUTO_PWM(data, i, j)); - data->auto_temp[i][j] = - data->read_value(data, - NCT6775_AUTO_TEMP(data, i, j)); + err = nct6775_read_value(data, NCT6775_AUTO_PWM(data, i, j), ®); + if (err) + return err; + data->auto_pwm[i][j] = reg; + + err = nct6775_read_value(data, NCT6775_AUTO_TEMP(data, i, j), ®); + if (err) + return err; + data->auto_temp[i][j] = reg; } /* critical auto_pwm temperature data */ - data->auto_temp[i][data->auto_pwm_num] = - data->read_value(data, data->REG_CRITICAL_TEMP[i]); + err = nct6775_read_value(data, data->REG_CRITICAL_TEMP[i], ®); + if (err) + return err; + data->auto_temp[i][data->auto_pwm_num] = reg; switch (data->kind) { case nct6775: - reg = data->read_value(data, - NCT6775_REG_CRITICAL_ENAB[i]); + err = nct6775_read_value(data, NCT6775_REG_CRITICAL_ENAB[i], ®); + if (err) + return err; data->auto_pwm[i][data->auto_pwm_num] = (reg & 0x02) ? 0xff : 0x00; break; @@ -1931,120 +1462,158 @@ static void nct6775_update_pwm_limits(struct device *dev) case nct6796: case nct6797: case nct6798: - reg = data->read_value(data, - data->REG_CRITICAL_PWM_ENABLE[i]); - if (reg & data->CRITICAL_PWM_ENABLE_MASK) - reg = data->read_value(data, - data->REG_CRITICAL_PWM[i]); - else + err = nct6775_read_value(data, data->REG_CRITICAL_PWM_ENABLE[i], ®); + if (err) + return err; + if (reg & data->CRITICAL_PWM_ENABLE_MASK) { + err = nct6775_read_value(data, data->REG_CRITICAL_PWM[i], ®); + if (err) + return err; + } else { reg = 0xff; + } data->auto_pwm[i][data->auto_pwm_num] = reg; break; } } + + return 0; } static struct nct6775_data *nct6775_update_device(struct device *dev) { struct nct6775_data *data = dev_get_drvdata(dev); - int i, j; + int i, j, err = 0; + u16 reg; mutex_lock(&data->update_lock); if (time_after(jiffies, data->last_updated + HZ + HZ / 2) || !data->valid) { /* Fan clock dividers */ - nct6775_update_fan_div_common(data); + err = nct6775_update_fan_div_common(data); + if (err) + goto out; /* Measured voltages and limits */ for (i = 0; i < data->in_num; i++) { if (!(data->have_in & BIT(i))) continue; - data->in[i][0] = data->read_value(data, - data->REG_VIN[i]); - data->in[i][1] = data->read_value(data, - data->REG_IN_MINMAX[0][i]); - data->in[i][2] = data->read_value(data, - data->REG_IN_MINMAX[1][i]); + err = nct6775_read_value(data, data->REG_VIN[i], ®); + if (err) + goto out; + data->in[i][0] = reg; + + err = nct6775_read_value(data, data->REG_IN_MINMAX[0][i], ®); + if (err) + goto out; + data->in[i][1] = reg; + + err = nct6775_read_value(data, data->REG_IN_MINMAX[1][i], ®); + if (err) + goto out; + data->in[i][2] = reg; } /* Measured fan speeds and limits */ for (i = 0; i < ARRAY_SIZE(data->rpm); i++) { - u16 reg; - if (!(data->has_fan & BIT(i))) continue; - reg = data->read_value(data, data->REG_FAN[i]); + err = nct6775_read_value(data, data->REG_FAN[i], ®); + if (err) + goto out; data->rpm[i] = data->fan_from_reg(reg, data->fan_div[i]); - if (data->has_fan_min & BIT(i)) - data->fan_min[i] = data->read_value(data, - data->REG_FAN_MIN[i]); - - if (data->REG_FAN_PULSES[i]) { - data->fan_pulses[i] = - (data->read_value(data, - data->REG_FAN_PULSES[i]) - >> data->FAN_PULSE_SHIFT[i]) & 0x03; + if (data->has_fan_min & BIT(i)) { + err = nct6775_read_value(data, data->REG_FAN_MIN[i], ®); + if (err) + goto out; + data->fan_min[i] = reg; } - nct6775_select_fan_div(dev, data, i, reg); + if (data->REG_FAN_PULSES[i]) { + err = nct6775_read_value(data, data->REG_FAN_PULSES[i], ®); + if (err) + goto out; + data->fan_pulses[i] = (reg >> data->FAN_PULSE_SHIFT[i]) & 0x03; + } + + err = nct6775_select_fan_div(dev, data, i, reg); + if (err) + goto out; } - nct6775_update_pwm(dev); - nct6775_update_pwm_limits(dev); + err = nct6775_update_pwm(dev); + if (err) + goto out; + + err = nct6775_update_pwm_limits(dev); + if (err) + goto out; /* Measured temperatures and limits */ for (i = 0; i < NUM_TEMP; i++) { if (!(data->have_temp & BIT(i))) continue; for (j = 0; j < ARRAY_SIZE(data->reg_temp); j++) { - if (data->reg_temp[j][i]) - data->temp[j][i] = nct6775_read_temp(data, - data->reg_temp[j][i]); + if (data->reg_temp[j][i]) { + err = nct6775_read_temp(data, data->reg_temp[j][i], ®); + if (err) + goto out; + data->temp[j][i] = reg; + } } if (i >= NUM_TEMP_FIXED || !(data->have_temp_fixed & BIT(i))) continue; - data->temp_offset[i] = data->read_value(data, - data->REG_TEMP_OFFSET[i]); + err = nct6775_read_value(data, data->REG_TEMP_OFFSET[i], ®); + if (err) + goto out; + data->temp_offset[i] = reg; } for (i = 0; i < NUM_TSI_TEMP; i++) { if (!(data->have_tsi_temp & BIT(i))) continue; - data->tsi_temp[i] = data->read_value(data, data->REG_TSI_TEMP[i]); + err = nct6775_read_value(data, data->REG_TSI_TEMP[i], ®); + if (err) + goto out; + data->tsi_temp[i] = reg; } data->alarms = 0; for (i = 0; i < NUM_REG_ALARM; i++) { - u8 alarm; + u16 alarm; if (!data->REG_ALARM[i]) continue; - alarm = data->read_value(data, data->REG_ALARM[i]); + err = nct6775_read_value(data, data->REG_ALARM[i], &alarm); + if (err) + goto out; data->alarms |= ((u64)alarm) << (i << 3); } data->beeps = 0; for (i = 0; i < NUM_REG_BEEP; i++) { - u8 beep; + u16 beep; if (!data->REG_BEEP[i]) continue; - beep = data->read_value(data, data->REG_BEEP[i]); + err = nct6775_read_value(data, data->REG_BEEP[i], &beep); + if (err) + goto out; data->beeps |= ((u64)beep) << (i << 3); } data->last_updated = jiffies; data->valid = true; } - +out: mutex_unlock(&data->update_lock); - return data; + return err ? ERR_PTR(err) : data; } /* @@ -2058,6 +1627,9 @@ show_in_reg(struct device *dev, struct device_attribute *attr, char *buf) int index = sattr->index; int nr = sattr->nr; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%ld\n", in_from_reg(data->in[nr][index], nr)); } @@ -2077,34 +1649,39 @@ store_in_reg(struct device *dev, struct device_attribute *attr, const char *buf, return err; mutex_lock(&data->update_lock); data->in[nr][index] = in_to_reg(val, nr); - data->write_value(data, data->REG_IN_MINMAX[index - 1][nr], - data->in[nr][index]); + err = nct6775_write_value(data, data->REG_IN_MINMAX[index - 1][nr], data->in[nr][index]); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } -static ssize_t -show_alarm(struct device *dev, struct device_attribute *attr, char *buf) +ssize_t +nct6775_show_alarm(struct device *dev, struct device_attribute *attr, char *buf) { struct nct6775_data *data = nct6775_update_device(dev); struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); - int nr = data->ALARM_BITS[sattr->index]; + int nr; + if (IS_ERR(data)) + return PTR_ERR(data); + + nr = data->ALARM_BITS[sattr->index]; return sprintf(buf, "%u\n", (unsigned int)((data->alarms >> nr) & 0x01)); } +EXPORT_SYMBOL_GPL(nct6775_show_alarm); static int find_temp_source(struct nct6775_data *data, int index, int count) { int source = data->temp_src[index]; - int nr; + int nr, err; for (nr = 0; nr < count; nr++) { - int src; + u16 src; - src = data->read_value(data, - data->REG_TEMP_SOURCE[nr]) & 0x1f; - if (src == source) + err = nct6775_read_value(data, data->REG_TEMP_SOURCE[nr], &src); + if (err) + return err; + if ((src & 0x1f) == source) return nr; } return -ENODEV; @@ -2118,6 +1695,9 @@ show_temp_alarm(struct device *dev, struct device_attribute *attr, char *buf) unsigned int alarm = 0; int nr; + if (IS_ERR(data)) + return PTR_ERR(data); + /* * For temperatures, there is no fixed mapping from registers to alarm * bits. Alarm bits are determined by the temperature source mapping. @@ -2131,20 +1711,25 @@ show_temp_alarm(struct device *dev, struct device_attribute *attr, char *buf) return sprintf(buf, "%u\n", alarm); } -static ssize_t -show_beep(struct device *dev, struct device_attribute *attr, char *buf) +ssize_t +nct6775_show_beep(struct device *dev, struct device_attribute *attr, char *buf) { struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); struct nct6775_data *data = nct6775_update_device(dev); - int nr = data->BEEP_BITS[sattr->index]; + int nr; + + if (IS_ERR(data)) + return PTR_ERR(data); + + nr = data->BEEP_BITS[sattr->index]; return sprintf(buf, "%u\n", (unsigned int)((data->beeps >> nr) & 0x01)); } +EXPORT_SYMBOL_GPL(nct6775_show_beep); -static ssize_t -store_beep(struct device *dev, struct device_attribute *attr, const char *buf, - size_t count) +ssize_t +nct6775_store_beep(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); struct nct6775_data *data = dev_get_drvdata(dev); @@ -2164,11 +1749,12 @@ store_beep(struct device *dev, struct device_attribute *attr, const char *buf, data->beeps |= (1ULL << nr); else data->beeps &= ~(1ULL << nr); - data->write_value(data, data->REG_BEEP[regindex], - (data->beeps >> (regindex << 3)) & 0xff); + err = nct6775_write_value(data, data->REG_BEEP[regindex], + (data->beeps >> (regindex << 3)) & 0xff); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } +EXPORT_SYMBOL_GPL(nct6775_store_beep); static ssize_t show_temp_beep(struct device *dev, struct device_attribute *attr, char *buf) @@ -2178,6 +1764,9 @@ show_temp_beep(struct device *dev, struct device_attribute *attr, char *buf) unsigned int beep = 0; int nr; + if (IS_ERR(data)) + return PTR_ERR(data); + /* * For temperatures, there is no fixed mapping from registers to beep * enable bits. Beep enable bits are determined by the temperature @@ -2220,11 +1809,11 @@ store_temp_beep(struct device *dev, struct device_attribute *attr, data->beeps |= (1ULL << bit); else data->beeps &= ~(1ULL << bit); - data->write_value(data, data->REG_BEEP[regindex], - (data->beeps >> (regindex << 3)) & 0xff); + err = nct6775_write_value(data, data->REG_BEEP[regindex], + (data->beeps >> (regindex << 3)) & 0xff); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static umode_t nct6775_in_is_visible(struct kobject *kobj, @@ -2237,17 +1826,14 @@ static umode_t nct6775_in_is_visible(struct kobject *kobj, if (!(data->have_in & BIT(in))) return 0; - return attr->mode; + return nct6775_attr_mode(data, attr); } -SENSOR_TEMPLATE_2(in_input, "in%d_input", S_IRUGO, show_in_reg, NULL, 0, 0); -SENSOR_TEMPLATE(in_alarm, "in%d_alarm", S_IRUGO, show_alarm, NULL, 0); -SENSOR_TEMPLATE(in_beep, "in%d_beep", S_IWUSR | S_IRUGO, show_beep, store_beep, - 0); -SENSOR_TEMPLATE_2(in_min, "in%d_min", S_IWUSR | S_IRUGO, show_in_reg, - store_in_reg, 0, 1); -SENSOR_TEMPLATE_2(in_max, "in%d_max", S_IWUSR | S_IRUGO, show_in_reg, - store_in_reg, 0, 2); +SENSOR_TEMPLATE_2(in_input, "in%d_input", 0444, show_in_reg, NULL, 0, 0); +SENSOR_TEMPLATE(in_alarm, "in%d_alarm", 0444, nct6775_show_alarm, NULL, 0); +SENSOR_TEMPLATE(in_beep, "in%d_beep", 0644, nct6775_show_beep, nct6775_store_beep, 0); +SENSOR_TEMPLATE_2(in_min, "in%d_min", 0644, show_in_reg, store_in_reg, 0, 1); +SENSOR_TEMPLATE_2(in_max, "in%d_max", 0644, show_in_reg, store_in_reg, 0, 2); /* * nct6775_in_is_visible uses the index into the following array @@ -2275,6 +1861,9 @@ show_fan(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->rpm[nr]); } @@ -2285,6 +1874,9 @@ show_fan_min(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->fan_from_reg_min(data->fan_min[nr], data->fan_div[nr])); @@ -2297,6 +1889,9 @@ show_fan_div(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%u\n", div_from_reg(data->fan_div[nr])); } @@ -2382,16 +1977,18 @@ write_div: nr + 1, div_from_reg(data->fan_div[nr]), div_from_reg(new_div)); data->fan_div[nr] = new_div; - nct6775_write_fan_div_common(data, nr); + err = nct6775_write_fan_div_common(data, nr); + if (err) + goto write_min; /* Give the chip time to sample a new speed value */ data->last_updated = jiffies; } write_min: - data->write_value(data, data->REG_FAN_MIN[nr], data->fan_min[nr]); + err = nct6775_write_value(data, data->REG_FAN_MIN[nr], data->fan_min[nr]); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -2399,8 +1996,12 @@ show_fan_pulses(struct device *dev, struct device_attribute *attr, char *buf) { struct nct6775_data *data = nct6775_update_device(dev); struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); - int p = data->fan_pulses[sattr->index]; + int p; + if (IS_ERR(data)) + return PTR_ERR(data); + + p = data->fan_pulses[sattr->index]; return sprintf(buf, "%d\n", p ? : 4); } @@ -2413,7 +2014,7 @@ store_fan_pulses(struct device *dev, struct device_attribute *attr, int nr = sattr->index; unsigned long val; int err; - u8 reg; + u16 reg; err = kstrtoul(buf, 10, &val); if (err < 0) @@ -2424,13 +2025,16 @@ store_fan_pulses(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->fan_pulses[nr] = val & 3; - reg = data->read_value(data, data->REG_FAN_PULSES[nr]); + err = nct6775_read_value(data, data->REG_FAN_PULSES[nr], ®); + if (err) + goto out; reg &= ~(0x03 << data->FAN_PULSE_SHIFT[nr]); reg |= (val & 3) << data->FAN_PULSE_SHIFT[nr]; - data->write_value(data, data->REG_FAN_PULSES[nr], reg); + err = nct6775_write_value(data, data->REG_FAN_PULSES[nr], reg); +out: mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static umode_t nct6775_fan_is_visible(struct kobject *kobj, @@ -2455,19 +2059,16 @@ static umode_t nct6775_fan_is_visible(struct kobject *kobj, if (nr == 5 && data->kind != nct6775) return 0; - return attr->mode; + return nct6775_attr_mode(data, attr); } -SENSOR_TEMPLATE(fan_input, "fan%d_input", S_IRUGO, show_fan, NULL, 0); -SENSOR_TEMPLATE(fan_alarm, "fan%d_alarm", S_IRUGO, show_alarm, NULL, - FAN_ALARM_BASE); -SENSOR_TEMPLATE(fan_beep, "fan%d_beep", S_IWUSR | S_IRUGO, show_beep, - store_beep, FAN_ALARM_BASE); -SENSOR_TEMPLATE(fan_pulses, "fan%d_pulses", S_IWUSR | S_IRUGO, show_fan_pulses, - store_fan_pulses, 0); -SENSOR_TEMPLATE(fan_min, "fan%d_min", S_IWUSR | S_IRUGO, show_fan_min, - store_fan_min, 0); -SENSOR_TEMPLATE(fan_div, "fan%d_div", S_IRUGO, show_fan_div, NULL, 0); +SENSOR_TEMPLATE(fan_input, "fan%d_input", 0444, show_fan, NULL, 0); +SENSOR_TEMPLATE(fan_alarm, "fan%d_alarm", 0444, nct6775_show_alarm, NULL, FAN_ALARM_BASE); +SENSOR_TEMPLATE(fan_beep, "fan%d_beep", 0644, nct6775_show_beep, + nct6775_store_beep, FAN_ALARM_BASE); +SENSOR_TEMPLATE(fan_pulses, "fan%d_pulses", 0644, show_fan_pulses, store_fan_pulses, 0); +SENSOR_TEMPLATE(fan_min, "fan%d_min", 0644, show_fan_min, store_fan_min, 0); +SENSOR_TEMPLATE(fan_div, "fan%d_div", 0444, show_fan_div, NULL, 0); /* * nct6775_fan_is_visible uses the index into the following array @@ -2497,6 +2098,9 @@ show_temp_label(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%s\n", data->temp_label[data->temp_src[nr]]); } @@ -2508,6 +2112,9 @@ show_temp(struct device *dev, struct device_attribute *attr, char *buf) int nr = sattr->nr; int index = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", LM75_TEMP_FROM_REG(data->temp[index][nr])); } @@ -2528,10 +2135,9 @@ store_temp(struct device *dev, struct device_attribute *attr, const char *buf, mutex_lock(&data->update_lock); data->temp[index][nr] = LM75_TEMP_TO_REG(val); - nct6775_write_temp(data, data->reg_temp[index][nr], - data->temp[index][nr]); + err = nct6775_write_temp(data, data->reg_temp[index][nr], data->temp[index][nr]); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -2540,6 +2146,9 @@ show_temp_offset(struct device *dev, struct device_attribute *attr, char *buf) struct nct6775_data *data = nct6775_update_device(dev); struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->temp_offset[sattr->index] * 1000); } @@ -2561,10 +2170,10 @@ store_temp_offset(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->temp_offset[nr] = val; - data->write_value(data, data->REG_TEMP_OFFSET[nr], val); + err = nct6775_write_value(data, data->REG_TEMP_OFFSET[nr], val); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -2574,6 +2183,9 @@ show_temp_type(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", (int)data->temp_type[nr]); } @@ -2586,7 +2198,11 @@ store_temp_type(struct device *dev, struct device_attribute *attr, int nr = sattr->index; unsigned long val; int err; - u8 vbat, diode, vbit, dbit; + u8 vbit, dbit; + u16 vbat, diode; + + if (IS_ERR(data)) + return PTR_ERR(data); err = kstrtoul(buf, 10, &val); if (err < 0) @@ -2600,8 +2216,17 @@ store_temp_type(struct device *dev, struct device_attribute *attr, data->temp_type[nr] = val; vbit = 0x02 << nr; dbit = data->DIODE_MASK << nr; - vbat = data->read_value(data, data->REG_VBAT) & ~vbit; - diode = data->read_value(data, data->REG_DIODE) & ~dbit; + + err = nct6775_read_value(data, data->REG_VBAT, &vbat); + if (err) + goto out; + vbat &= ~vbit; + + err = nct6775_read_value(data, data->REG_DIODE, &diode); + if (err) + goto out; + diode &= ~dbit; + switch (val) { case 1: /* CPU diode (diode, current mode) */ vbat |= vbit; @@ -2613,11 +2238,13 @@ store_temp_type(struct device *dev, struct device_attribute *attr, case 4: /* thermistor */ break; } - data->write_value(data, data->REG_VBAT, vbat); - data->write_value(data, data->REG_DIODE, diode); - + err = nct6775_write_value(data, data->REG_VBAT, vbat); + if (err) + goto out; + err = nct6775_write_value(data, data->REG_DIODE, diode); +out: mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static umode_t nct6775_temp_is_visible(struct kobject *kobj, @@ -2656,26 +2283,19 @@ static umode_t nct6775_temp_is_visible(struct kobject *kobj, if (nr > 7 && !(data->have_temp_fixed & BIT(temp))) return 0; - return attr->mode; + return nct6775_attr_mode(data, attr); } -SENSOR_TEMPLATE_2(temp_input, "temp%d_input", S_IRUGO, show_temp, NULL, 0, 0); -SENSOR_TEMPLATE(temp_label, "temp%d_label", S_IRUGO, show_temp_label, NULL, 0); -SENSOR_TEMPLATE_2(temp_max, "temp%d_max", S_IRUGO | S_IWUSR, show_temp, - store_temp, 0, 1); -SENSOR_TEMPLATE_2(temp_max_hyst, "temp%d_max_hyst", S_IRUGO | S_IWUSR, - show_temp, store_temp, 0, 2); -SENSOR_TEMPLATE_2(temp_crit, "temp%d_crit", S_IRUGO | S_IWUSR, show_temp, - store_temp, 0, 3); -SENSOR_TEMPLATE_2(temp_lcrit, "temp%d_lcrit", S_IRUGO | S_IWUSR, show_temp, - store_temp, 0, 4); -SENSOR_TEMPLATE(temp_offset, "temp%d_offset", S_IRUGO | S_IWUSR, - show_temp_offset, store_temp_offset, 0); -SENSOR_TEMPLATE(temp_type, "temp%d_type", S_IRUGO | S_IWUSR, show_temp_type, - store_temp_type, 0); -SENSOR_TEMPLATE(temp_alarm, "temp%d_alarm", S_IRUGO, show_temp_alarm, NULL, 0); -SENSOR_TEMPLATE(temp_beep, "temp%d_beep", S_IRUGO | S_IWUSR, show_temp_beep, - store_temp_beep, 0); +SENSOR_TEMPLATE_2(temp_input, "temp%d_input", 0444, show_temp, NULL, 0, 0); +SENSOR_TEMPLATE(temp_label, "temp%d_label", 0444, show_temp_label, NULL, 0); +SENSOR_TEMPLATE_2(temp_max, "temp%d_max", 0644, show_temp, store_temp, 0, 1); +SENSOR_TEMPLATE_2(temp_max_hyst, "temp%d_max_hyst", 0644, show_temp, store_temp, 0, 2); +SENSOR_TEMPLATE_2(temp_crit, "temp%d_crit", 0644, show_temp, store_temp, 0, 3); +SENSOR_TEMPLATE_2(temp_lcrit, "temp%d_lcrit", 0644, show_temp, store_temp, 0, 4); +SENSOR_TEMPLATE(temp_offset, "temp%d_offset", 0644, show_temp_offset, store_temp_offset, 0); +SENSOR_TEMPLATE(temp_type, "temp%d_type", 0644, show_temp_type, store_temp_type, 0); +SENSOR_TEMPLATE(temp_alarm, "temp%d_alarm", 0444, show_temp_alarm, NULL, 0); +SENSOR_TEMPLATE(temp_beep, "temp%d_beep", 0644, show_temp_beep, store_temp_beep, 0); /* * nct6775_temp_is_visible uses the index into the following array @@ -2707,6 +2327,9 @@ static ssize_t show_tsi_temp(struct device *dev, struct device_attribute *attr, struct nct6775_data *data = nct6775_update_device(dev); struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + if (IS_ERR(data)) + return PTR_ERR(data); + return sysfs_emit(buf, "%u\n", tsi_temp_from_reg(data->tsi_temp[sattr->index])); } @@ -2727,7 +2350,7 @@ static umode_t nct6775_tsi_temp_is_visible(struct kobject *kobj, struct attribut struct nct6775_data *data = dev_get_drvdata(dev); int temp = index / 2; - return (data->have_tsi_temp & BIT(temp)) ? attr->mode : 0; + return (data->have_tsi_temp & BIT(temp)) ? nct6775_attr_mode(data, attr) : 0; } /* @@ -2746,6 +2369,9 @@ show_pwm_mode(struct device *dev, struct device_attribute *attr, char *buf) struct nct6775_data *data = nct6775_update_device(dev); struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->pwm_mode[sattr->index]); } @@ -2758,7 +2384,7 @@ store_pwm_mode(struct device *dev, struct device_attribute *attr, int nr = sattr->index; unsigned long val; int err; - u8 reg; + u16 reg; err = kstrtoul(buf, 10, &val); if (err < 0) @@ -2776,13 +2402,16 @@ store_pwm_mode(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->pwm_mode[nr] = val; - reg = data->read_value(data, data->REG_PWM_MODE[nr]); + err = nct6775_read_value(data, data->REG_PWM_MODE[nr], ®); + if (err) + goto out; reg &= ~data->PWM_MODE_MASK[nr]; if (!val) reg |= data->PWM_MODE_MASK[nr]; - data->write_value(data, data->REG_PWM_MODE[nr], reg); + err = nct6775_write_value(data, data->REG_PWM_MODE[nr], reg); +out: mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -2792,16 +2421,23 @@ show_pwm(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); int nr = sattr->nr; int index = sattr->index; - int pwm; + int err; + u16 pwm; + + if (IS_ERR(data)) + return PTR_ERR(data); /* * For automatic fan control modes, show current pwm readings. * Otherwise, show the configured value. */ - if (index == 0 && data->pwm_enable[nr] > manual) - pwm = data->read_value(data, data->REG_PWM_READ[nr]); - else + if (index == 0 && data->pwm_enable[nr] > manual) { + err = nct6775_read_value(data, data->REG_PWM_READ[nr], &pwm); + if (err) + return err; + } else { pwm = data->pwm[index][nr]; + } return sprintf(buf, "%d\n", pwm); } @@ -2819,7 +2455,7 @@ store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, int maxval[7] = { 255, 255, data->pwm[3][nr] ? : 255, 255, 255, 255, 255 }; int err; - u8 reg; + u16 reg; err = kstrtoul(buf, 10, &val); if (err < 0) @@ -2828,16 +2464,21 @@ store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, mutex_lock(&data->update_lock); data->pwm[index][nr] = val; - data->write_value(data, data->REG_PWM[index][nr], val); + err = nct6775_write_value(data, data->REG_PWM[index][nr], val); + if (err) + goto out; if (index == 2) { /* floor: disable if val == 0 */ - reg = data->read_value(data, data->REG_TEMP_SEL[nr]); + err = nct6775_read_value(data, data->REG_TEMP_SEL[nr], ®); + if (err) + goto out; reg &= 0x7f; if (val) reg |= 0x80; - data->write_value(data, data->REG_TEMP_SEL[nr], reg); + err = nct6775_write_value(data, data->REG_TEMP_SEL[nr], reg); } +out: mutex_unlock(&data->update_lock); - return count; + return err ? : count; } /* Returns 0 if OK, -EINVAL otherwise */ @@ -2864,40 +2505,54 @@ static int check_trip_points(struct nct6775_data *data, int nr) return 0; } -static void pwm_update_registers(struct nct6775_data *data, int nr) +static int pwm_update_registers(struct nct6775_data *data, int nr) { - u8 reg; + u16 reg; + int err; switch (data->pwm_enable[nr]) { case off: case manual: break; case speed_cruise: - reg = data->read_value(data, data->REG_FAN_MODE[nr]); + err = nct6775_read_value(data, data->REG_FAN_MODE[nr], ®); + if (err) + return err; reg = (reg & ~data->tolerance_mask) | (data->target_speed_tolerance[nr] & data->tolerance_mask); - data->write_value(data, data->REG_FAN_MODE[nr], reg); - data->write_value(data, data->REG_TARGET[nr], - data->target_speed[nr] & 0xff); + err = nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); + if (err) + return err; + err = nct6775_write_value(data, data->REG_TARGET[nr], + data->target_speed[nr] & 0xff); + if (err) + return err; if (data->REG_TOLERANCE_H) { reg = (data->target_speed[nr] >> 8) & 0x0f; reg |= (data->target_speed_tolerance[nr] & 0x38) << 1; - data->write_value(data, - data->REG_TOLERANCE_H[nr], - reg); + err = nct6775_write_value(data, data->REG_TOLERANCE_H[nr], reg); + if (err) + return err; } break; case thermal_cruise: - data->write_value(data, data->REG_TARGET[nr], - data->target_temp[nr]); + err = nct6775_write_value(data, data->REG_TARGET[nr], data->target_temp[nr]); + if (err) + return err; fallthrough; default: - reg = data->read_value(data, data->REG_FAN_MODE[nr]); + err = nct6775_read_value(data, data->REG_FAN_MODE[nr], ®); + if (err) + return err; reg = (reg & ~data->tolerance_mask) | data->temp_tolerance[0][nr]; - data->write_value(data, data->REG_FAN_MODE[nr], reg); + err = nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); + if (err) + return err; break; } + + return 0; } static ssize_t @@ -2906,6 +2561,9 @@ show_pwm_enable(struct device *dev, struct device_attribute *attr, char *buf) struct nct6775_data *data = nct6775_update_device(dev); struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->pwm_enable[sattr->index]); } @@ -2943,15 +2601,22 @@ store_pwm_enable(struct device *dev, struct device_attribute *attr, * turn off pwm control: select manual mode, set pwm to maximum */ data->pwm[0][nr] = 255; - data->write_value(data, data->REG_PWM[0][nr], 255); + err = nct6775_write_value(data, data->REG_PWM[0][nr], 255); + if (err) + goto out; } - pwm_update_registers(data, nr); - reg = data->read_value(data, data->REG_FAN_MODE[nr]); + err = pwm_update_registers(data, nr); + if (err) + goto out; + err = nct6775_read_value(data, data->REG_FAN_MODE[nr], ®); + if (err) + goto out; reg &= 0x0f; reg |= pwm_enable_to_reg(val) << 4; - data->write_value(data, data->REG_FAN_MODE[nr], reg); + err = nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); +out: mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -2978,6 +2643,9 @@ show_pwm_temp_sel(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int index = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return show_pwm_temp_sel_common(data, buf, data->pwm_temp_sel[index]); } @@ -2989,7 +2657,11 @@ store_pwm_temp_sel(struct device *dev, struct device_attribute *attr, struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; unsigned long val; - int err, reg, src; + int err, src; + u16 reg; + + if (IS_ERR(data)) + return PTR_ERR(data); err = kstrtoul(buf, 10, &val); if (err < 0) @@ -3002,13 +2674,16 @@ store_pwm_temp_sel(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); src = data->temp_src[val - 1]; data->pwm_temp_sel[nr] = src; - reg = data->read_value(data, data->REG_TEMP_SEL[nr]); + err = nct6775_read_value(data, data->REG_TEMP_SEL[nr], ®); + if (err) + goto out; reg &= 0xe0; reg |= src; - data->write_value(data, data->REG_TEMP_SEL[nr], reg); + err = nct6775_write_value(data, data->REG_TEMP_SEL[nr], reg); +out: mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -3019,6 +2694,9 @@ show_pwm_weight_temp_sel(struct device *dev, struct device_attribute *attr, struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int index = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return show_pwm_temp_sel_common(data, buf, data->pwm_weight_temp_sel[index]); } @@ -3031,7 +2709,11 @@ store_pwm_weight_temp_sel(struct device *dev, struct device_attribute *attr, struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; unsigned long val; - int err, reg, src; + int err, src; + u16 reg; + + if (IS_ERR(data)) + return PTR_ERR(data); err = kstrtoul(buf, 10, &val); if (err < 0) @@ -3047,19 +2729,24 @@ store_pwm_weight_temp_sel(struct device *dev, struct device_attribute *attr, if (val) { src = data->temp_src[val - 1]; data->pwm_weight_temp_sel[nr] = src; - reg = data->read_value(data, data->REG_WEIGHT_TEMP_SEL[nr]); + err = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[nr], ®); + if (err) + goto out; reg &= 0xe0; reg |= (src | 0x80); - data->write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); + err = nct6775_write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); } else { data->pwm_weight_temp_sel[nr] = 0; - reg = data->read_value(data, data->REG_WEIGHT_TEMP_SEL[nr]); + err = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[nr], ®); + if (err) + goto out; reg &= 0x7f; - data->write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); + err = nct6775_write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); } +out: mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -3068,6 +2755,9 @@ show_target_temp(struct device *dev, struct device_attribute *attr, char *buf) struct nct6775_data *data = nct6775_update_device(dev); struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->target_temp[sattr->index] * 1000); } @@ -3090,9 +2780,9 @@ store_target_temp(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->target_temp[nr] = val; - pwm_update_registers(data, nr); + err = pwm_update_registers(data, nr); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -3102,6 +2792,9 @@ show_target_speed(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", fan_from_reg16(data->target_speed[nr], data->fan_div[nr])); @@ -3127,9 +2820,9 @@ store_target_speed(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->target_speed[nr] = speed; - pwm_update_registers(data, nr); + err = pwm_update_registers(data, nr); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -3141,6 +2834,9 @@ show_temp_tolerance(struct device *dev, struct device_attribute *attr, int nr = sattr->nr; int index = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->temp_tolerance[index][nr] * 1000); } @@ -3165,13 +2861,11 @@ store_temp_tolerance(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->temp_tolerance[index][nr] = val; if (index) - pwm_update_registers(data, nr); + err = pwm_update_registers(data, nr); else - data->write_value(data, - data->REG_CRITICAL_TEMP_TOLERANCE[nr], - val); + err = nct6775_write_value(data, data->REG_CRITICAL_TEMP_TOLERANCE[nr], val); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } /* @@ -3188,8 +2882,12 @@ show_speed_tolerance(struct device *dev, struct device_attribute *attr, struct nct6775_data *data = nct6775_update_device(dev); struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; - int target = data->target_speed[nr]; - int tolerance = 0; + int target, tolerance = 0; + + if (IS_ERR(data)) + return PTR_ERR(data); + + target = data->target_speed[nr]; if (target) { int low = target - data->target_speed_tolerance[nr]; @@ -3239,24 +2937,19 @@ store_speed_tolerance(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->target_speed_tolerance[nr] = val; - pwm_update_registers(data, nr); + err = pwm_update_registers(data, nr); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } -SENSOR_TEMPLATE_2(pwm, "pwm%d", S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0, 0); -SENSOR_TEMPLATE(pwm_mode, "pwm%d_mode", S_IWUSR | S_IRUGO, show_pwm_mode, - store_pwm_mode, 0); -SENSOR_TEMPLATE(pwm_enable, "pwm%d_enable", S_IWUSR | S_IRUGO, show_pwm_enable, - store_pwm_enable, 0); -SENSOR_TEMPLATE(pwm_temp_sel, "pwm%d_temp_sel", S_IWUSR | S_IRUGO, - show_pwm_temp_sel, store_pwm_temp_sel, 0); -SENSOR_TEMPLATE(pwm_target_temp, "pwm%d_target_temp", S_IWUSR | S_IRUGO, - show_target_temp, store_target_temp, 0); -SENSOR_TEMPLATE(fan_target, "fan%d_target", S_IWUSR | S_IRUGO, - show_target_speed, store_target_speed, 0); -SENSOR_TEMPLATE(fan_tolerance, "fan%d_tolerance", S_IWUSR | S_IRUGO, - show_speed_tolerance, store_speed_tolerance, 0); +SENSOR_TEMPLATE_2(pwm, "pwm%d", 0644, show_pwm, store_pwm, 0, 0); +SENSOR_TEMPLATE(pwm_mode, "pwm%d_mode", 0644, show_pwm_mode, store_pwm_mode, 0); +SENSOR_TEMPLATE(pwm_enable, "pwm%d_enable", 0644, show_pwm_enable, store_pwm_enable, 0); +SENSOR_TEMPLATE(pwm_temp_sel, "pwm%d_temp_sel", 0644, show_pwm_temp_sel, store_pwm_temp_sel, 0); +SENSOR_TEMPLATE(pwm_target_temp, "pwm%d_target_temp", 0644, show_target_temp, store_target_temp, 0); +SENSOR_TEMPLATE(fan_target, "fan%d_target", 0644, show_target_speed, store_target_speed, 0); +SENSOR_TEMPLATE(fan_tolerance, "fan%d_tolerance", 0644, show_speed_tolerance, + store_speed_tolerance, 0); /* Smart Fan registers */ @@ -3268,6 +2961,9 @@ show_weight_temp(struct device *dev, struct device_attribute *attr, char *buf) int nr = sattr->nr; int index = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->weight_temp[index][nr] * 1000); } @@ -3290,23 +2986,21 @@ store_weight_temp(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->weight_temp[index][nr] = val; - data->write_value(data, data->REG_WEIGHT_TEMP[index][nr], val); + err = nct6775_write_value(data, data->REG_WEIGHT_TEMP[index][nr], val); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } -SENSOR_TEMPLATE(pwm_weight_temp_sel, "pwm%d_weight_temp_sel", S_IWUSR | S_IRUGO, - show_pwm_weight_temp_sel, store_pwm_weight_temp_sel, 0); +SENSOR_TEMPLATE(pwm_weight_temp_sel, "pwm%d_weight_temp_sel", 0644, + show_pwm_weight_temp_sel, store_pwm_weight_temp_sel, 0); SENSOR_TEMPLATE_2(pwm_weight_temp_step, "pwm%d_weight_temp_step", - S_IWUSR | S_IRUGO, show_weight_temp, store_weight_temp, 0, 0); + 0644, show_weight_temp, store_weight_temp, 0, 0); SENSOR_TEMPLATE_2(pwm_weight_temp_step_tol, "pwm%d_weight_temp_step_tol", - S_IWUSR | S_IRUGO, show_weight_temp, store_weight_temp, 0, 1); + 0644, show_weight_temp, store_weight_temp, 0, 1); SENSOR_TEMPLATE_2(pwm_weight_temp_step_base, "pwm%d_weight_temp_step_base", - S_IWUSR | S_IRUGO, show_weight_temp, store_weight_temp, 0, 2); -SENSOR_TEMPLATE_2(pwm_weight_duty_step, "pwm%d_weight_duty_step", - S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0, 5); -SENSOR_TEMPLATE_2(pwm_weight_duty_base, "pwm%d_weight_duty_base", - S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0, 6); + 0644, show_weight_temp, store_weight_temp, 0, 2); +SENSOR_TEMPLATE_2(pwm_weight_duty_step, "pwm%d_weight_duty_step", 0644, show_pwm, store_pwm, 0, 5); +SENSOR_TEMPLATE_2(pwm_weight_duty_base, "pwm%d_weight_duty_base", 0644, show_pwm, store_pwm, 0, 6); static ssize_t show_fan_time(struct device *dev, struct device_attribute *attr, char *buf) @@ -3316,6 +3010,9 @@ show_fan_time(struct device *dev, struct device_attribute *attr, char *buf) int nr = sattr->nr; int index = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", step_time_from_reg(data->fan_time[index][nr], data->pwm_mode[nr])); @@ -3339,9 +3036,9 @@ store_fan_time(struct device *dev, struct device_attribute *attr, val = step_time_to_reg(val, data->pwm_mode[nr]); mutex_lock(&data->update_lock); data->fan_time[index][nr] = val; - data->write_value(data, data->REG_FAN_TIME[index][nr], val); + err = nct6775_write_value(data, data->REG_FAN_TIME[index][nr], val); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -3350,6 +3047,9 @@ show_auto_pwm(struct device *dev, struct device_attribute *attr, char *buf) struct nct6775_data *data = nct6775_update_device(dev); struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->auto_pwm[sattr->nr][sattr->index]); } @@ -3363,7 +3063,7 @@ store_auto_pwm(struct device *dev, struct device_attribute *attr, int point = sattr->index; unsigned long val; int err; - u8 reg; + u16 reg; err = kstrtoul(buf, 10, &val); if (err < 0) @@ -3381,21 +3081,20 @@ store_auto_pwm(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->auto_pwm[nr][point] = val; if (point < data->auto_pwm_num) { - data->write_value(data, - NCT6775_AUTO_PWM(data, nr, point), - data->auto_pwm[nr][point]); + err = nct6775_write_value(data, NCT6775_AUTO_PWM(data, nr, point), + data->auto_pwm[nr][point]); } else { switch (data->kind) { case nct6775: /* disable if needed (pwm == 0) */ - reg = data->read_value(data, - NCT6775_REG_CRITICAL_ENAB[nr]); + err = nct6775_read_value(data, NCT6775_REG_CRITICAL_ENAB[nr], ®); + if (err) + break; if (val) reg |= 0x02; else reg &= ~0x02; - data->write_value(data, NCT6775_REG_CRITICAL_ENAB[nr], - reg); + err = nct6775_write_value(data, NCT6775_REG_CRITICAL_ENAB[nr], reg); break; case nct6776: break; /* always enabled, nothing to do */ @@ -3409,22 +3108,22 @@ store_auto_pwm(struct device *dev, struct device_attribute *attr, case nct6796: case nct6797: case nct6798: - data->write_value(data, data->REG_CRITICAL_PWM[nr], - val); - reg = data->read_value(data, - data->REG_CRITICAL_PWM_ENABLE[nr]); + err = nct6775_write_value(data, data->REG_CRITICAL_PWM[nr], val); + if (err) + break; + err = nct6775_read_value(data, data->REG_CRITICAL_PWM_ENABLE[nr], ®); + if (err) + break; if (val == 255) reg &= ~data->CRITICAL_PWM_ENABLE_MASK; else reg |= data->CRITICAL_PWM_ENABLE_MASK; - data->write_value(data, - data->REG_CRITICAL_PWM_ENABLE[nr], - reg); + err = nct6775_write_value(data, data->REG_CRITICAL_PWM_ENABLE[nr], reg); break; } } mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -3435,6 +3134,9 @@ show_auto_temp(struct device *dev, struct device_attribute *attr, char *buf) int nr = sattr->nr; int point = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + /* * We don't know for sure if the temperature is signed or unsigned. * Assume it is unsigned. @@ -3462,15 +3164,14 @@ store_auto_temp(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->auto_temp[nr][point] = DIV_ROUND_CLOSEST(val, 1000); if (point < data->auto_pwm_num) { - data->write_value(data, - NCT6775_AUTO_TEMP(data, nr, point), - data->auto_temp[nr][point]); + err = nct6775_write_value(data, NCT6775_AUTO_TEMP(data, nr, point), + data->auto_temp[nr][point]); } else { - data->write_value(data, data->REG_CRITICAL_TEMP[nr], - data->auto_temp[nr][point]); + err = nct6775_write_value(data, data->REG_CRITICAL_TEMP[nr], + data->auto_temp[nr][point]); } mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static umode_t nct6775_pwm_is_visible(struct kobject *kobj, @@ -3500,65 +3201,59 @@ static umode_t nct6775_pwm_is_visible(struct kobject *kobj, if (api > data->auto_pwm_num) return 0; } - return attr->mode; + return nct6775_attr_mode(data, attr); } -SENSOR_TEMPLATE_2(pwm_stop_time, "pwm%d_stop_time", S_IWUSR | S_IRUGO, - show_fan_time, store_fan_time, 0, 0); -SENSOR_TEMPLATE_2(pwm_step_up_time, "pwm%d_step_up_time", S_IWUSR | S_IRUGO, +SENSOR_TEMPLATE_2(pwm_stop_time, "pwm%d_stop_time", 0644, show_fan_time, store_fan_time, 0, 0); +SENSOR_TEMPLATE_2(pwm_step_up_time, "pwm%d_step_up_time", 0644, show_fan_time, store_fan_time, 0, 1); -SENSOR_TEMPLATE_2(pwm_step_down_time, "pwm%d_step_down_time", S_IWUSR | S_IRUGO, +SENSOR_TEMPLATE_2(pwm_step_down_time, "pwm%d_step_down_time", 0644, show_fan_time, store_fan_time, 0, 2); -SENSOR_TEMPLATE_2(pwm_start, "pwm%d_start", S_IWUSR | S_IRUGO, show_pwm, - store_pwm, 0, 1); -SENSOR_TEMPLATE_2(pwm_floor, "pwm%d_floor", S_IWUSR | S_IRUGO, show_pwm, - store_pwm, 0, 2); -SENSOR_TEMPLATE_2(pwm_temp_tolerance, "pwm%d_temp_tolerance", S_IWUSR | S_IRUGO, +SENSOR_TEMPLATE_2(pwm_start, "pwm%d_start", 0644, show_pwm, store_pwm, 0, 1); +SENSOR_TEMPLATE_2(pwm_floor, "pwm%d_floor", 0644, show_pwm, store_pwm, 0, 2); +SENSOR_TEMPLATE_2(pwm_temp_tolerance, "pwm%d_temp_tolerance", 0644, show_temp_tolerance, store_temp_tolerance, 0, 0); SENSOR_TEMPLATE_2(pwm_crit_temp_tolerance, "pwm%d_crit_temp_tolerance", - S_IWUSR | S_IRUGO, show_temp_tolerance, store_temp_tolerance, - 0, 1); + 0644, show_temp_tolerance, store_temp_tolerance, 0, 1); -SENSOR_TEMPLATE_2(pwm_max, "pwm%d_max", S_IWUSR | S_IRUGO, show_pwm, store_pwm, - 0, 3); +SENSOR_TEMPLATE_2(pwm_max, "pwm%d_max", 0644, show_pwm, store_pwm, 0, 3); -SENSOR_TEMPLATE_2(pwm_step, "pwm%d_step", S_IWUSR | S_IRUGO, show_pwm, - store_pwm, 0, 4); +SENSOR_TEMPLATE_2(pwm_step, "pwm%d_step", 0644, show_pwm, store_pwm, 0, 4); SENSOR_TEMPLATE_2(pwm_auto_point1_pwm, "pwm%d_auto_point1_pwm", - S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 0); + 0644, show_auto_pwm, store_auto_pwm, 0, 0); SENSOR_TEMPLATE_2(pwm_auto_point1_temp, "pwm%d_auto_point1_temp", - S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 0); + 0644, show_auto_temp, store_auto_temp, 0, 0); SENSOR_TEMPLATE_2(pwm_auto_point2_pwm, "pwm%d_auto_point2_pwm", - S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 1); + 0644, show_auto_pwm, store_auto_pwm, 0, 1); SENSOR_TEMPLATE_2(pwm_auto_point2_temp, "pwm%d_auto_point2_temp", - S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 1); + 0644, show_auto_temp, store_auto_temp, 0, 1); SENSOR_TEMPLATE_2(pwm_auto_point3_pwm, "pwm%d_auto_point3_pwm", - S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 2); + 0644, show_auto_pwm, store_auto_pwm, 0, 2); SENSOR_TEMPLATE_2(pwm_auto_point3_temp, "pwm%d_auto_point3_temp", - S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 2); + 0644, show_auto_temp, store_auto_temp, 0, 2); SENSOR_TEMPLATE_2(pwm_auto_point4_pwm, "pwm%d_auto_point4_pwm", - S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 3); + 0644, show_auto_pwm, store_auto_pwm, 0, 3); SENSOR_TEMPLATE_2(pwm_auto_point4_temp, "pwm%d_auto_point4_temp", - S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 3); + 0644, show_auto_temp, store_auto_temp, 0, 3); SENSOR_TEMPLATE_2(pwm_auto_point5_pwm, "pwm%d_auto_point5_pwm", - S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 4); + 0644, show_auto_pwm, store_auto_pwm, 0, 4); SENSOR_TEMPLATE_2(pwm_auto_point5_temp, "pwm%d_auto_point5_temp", - S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 4); + 0644, show_auto_temp, store_auto_temp, 0, 4); SENSOR_TEMPLATE_2(pwm_auto_point6_pwm, "pwm%d_auto_point6_pwm", - S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 5); + 0644, show_auto_pwm, store_auto_pwm, 0, 5); SENSOR_TEMPLATE_2(pwm_auto_point6_temp, "pwm%d_auto_point6_temp", - S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 5); + 0644, show_auto_temp, store_auto_temp, 0, 5); SENSOR_TEMPLATE_2(pwm_auto_point7_pwm, "pwm%d_auto_point7_pwm", - S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 6); + 0644, show_auto_pwm, store_auto_pwm, 0, 6); SENSOR_TEMPLATE_2(pwm_auto_point7_temp, "pwm%d_auto_point7_temp", - S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 6); + 0644, show_auto_temp, store_auto_temp, 0, 6); /* * nct6775_pwm_is_visible uses the index into the following array @@ -3612,123 +3307,21 @@ static const struct sensor_template_group nct6775_pwm_template_group = { .base = 1, }; -static ssize_t -cpu0_vid_show(struct device *dev, struct device_attribute *attr, char *buf) +static inline int nct6775_init_device(struct nct6775_data *data) { - struct nct6775_data *data = dev_get_drvdata(dev); - - return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); -} - -static DEVICE_ATTR_RO(cpu0_vid); - -/* Case open detection */ - -static ssize_t -clear_caseopen(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct nct6775_data *data = dev_get_drvdata(dev); - struct nct6775_sio_data *sio_data = data->sio_data; - int nr = to_sensor_dev_attr(attr)->index - INTRUSION_ALARM_BASE; - unsigned long val; - u8 reg; - int ret; - - if (kstrtoul(buf, 10, &val) || val != 0) - return -EINVAL; - - mutex_lock(&data->update_lock); - - /* - * Use CR registers to clear caseopen status. - * The CR registers are the same for all chips, and not all chips - * support clearing the caseopen status through "regular" registers. - */ - ret = sio_data->sio_enter(sio_data); - if (ret) { - count = ret; - goto error; - } - - sio_data->sio_select(sio_data, NCT6775_LD_ACPI); - reg = sio_data->sio_inb(sio_data, NCT6775_REG_CR_CASEOPEN_CLR[nr]); - reg |= NCT6775_CR_CASEOPEN_CLR_MASK[nr]; - sio_data->sio_outb(sio_data, NCT6775_REG_CR_CASEOPEN_CLR[nr], reg); - reg &= ~NCT6775_CR_CASEOPEN_CLR_MASK[nr]; - sio_data->sio_outb(sio_data, NCT6775_REG_CR_CASEOPEN_CLR[nr], reg); - sio_data->sio_exit(sio_data); - - data->valid = false; /* Force cache refresh */ -error: - mutex_unlock(&data->update_lock); - return count; -} - -static SENSOR_DEVICE_ATTR(intrusion0_alarm, S_IWUSR | S_IRUGO, show_alarm, - clear_caseopen, INTRUSION_ALARM_BASE); -static SENSOR_DEVICE_ATTR(intrusion1_alarm, S_IWUSR | S_IRUGO, show_alarm, - clear_caseopen, INTRUSION_ALARM_BASE + 1); -static SENSOR_DEVICE_ATTR(intrusion0_beep, S_IWUSR | S_IRUGO, show_beep, - store_beep, INTRUSION_ALARM_BASE); -static SENSOR_DEVICE_ATTR(intrusion1_beep, S_IWUSR | S_IRUGO, show_beep, - store_beep, INTRUSION_ALARM_BASE + 1); -static SENSOR_DEVICE_ATTR(beep_enable, S_IWUSR | S_IRUGO, show_beep, - store_beep, BEEP_ENABLE_BASE); - -static umode_t nct6775_other_is_visible(struct kobject *kobj, - struct attribute *attr, int index) -{ - struct device *dev = kobj_to_dev(kobj); - struct nct6775_data *data = dev_get_drvdata(dev); - - if (index == 0 && !data->have_vid) - return 0; - - if (index == 1 || index == 2) { - if (data->ALARM_BITS[INTRUSION_ALARM_BASE + index - 1] < 0) - return 0; - } - - if (index == 3 || index == 4) { - if (data->BEEP_BITS[INTRUSION_ALARM_BASE + index - 3] < 0) - return 0; - } - - return attr->mode; -} - -/* - * nct6775_other_is_visible uses the index into the following array - * to determine if attributes should be created or not. - * Any change in order or content must be matched. - */ -static struct attribute *nct6775_attributes_other[] = { - &dev_attr_cpu0_vid.attr, /* 0 */ - &sensor_dev_attr_intrusion0_alarm.dev_attr.attr, /* 1 */ - &sensor_dev_attr_intrusion1_alarm.dev_attr.attr, /* 2 */ - &sensor_dev_attr_intrusion0_beep.dev_attr.attr, /* 3 */ - &sensor_dev_attr_intrusion1_beep.dev_attr.attr, /* 4 */ - &sensor_dev_attr_beep_enable.dev_attr.attr, /* 5 */ - - NULL -}; - -static const struct attribute_group nct6775_group_other = { - .attrs = nct6775_attributes_other, - .is_visible = nct6775_other_is_visible, -}; - -static inline void nct6775_init_device(struct nct6775_data *data) -{ - int i; - u8 tmp, diode; + int i, err; + u16 tmp, diode; /* Start monitoring if needed */ if (data->REG_CONFIG) { - tmp = data->read_value(data, data->REG_CONFIG); - if (!(tmp & 0x01)) - data->write_value(data, data->REG_CONFIG, tmp | 0x01); + err = nct6775_read_value(data, data->REG_CONFIG, &tmp); + if (err) + return err; + if (!(tmp & 0x01)) { + err = nct6775_write_value(data, data->REG_CONFIG, tmp | 0x01); + if (err) + return err; + } } /* Enable temperature sensors if needed */ @@ -3737,18 +3330,29 @@ static inline void nct6775_init_device(struct nct6775_data *data) continue; if (!data->reg_temp_config[i]) continue; - tmp = data->read_value(data, data->reg_temp_config[i]); - if (tmp & 0x01) - data->write_value(data, data->reg_temp_config[i], - tmp & 0xfe); + err = nct6775_read_value(data, data->reg_temp_config[i], &tmp); + if (err) + return err; + if (tmp & 0x01) { + err = nct6775_write_value(data, data->reg_temp_config[i], tmp & 0xfe); + if (err) + return err; + } } /* Enable VBAT monitoring if needed */ - tmp = data->read_value(data, data->REG_VBAT); - if (!(tmp & 0x01)) - data->write_value(data, data->REG_VBAT, tmp | 0x01); + err = nct6775_read_value(data, data->REG_VBAT, &tmp); + if (err) + return err; + if (!(tmp & 0x01)) { + err = nct6775_write_value(data, data->REG_VBAT, tmp | 0x01); + if (err) + return err; + } - diode = data->read_value(data, data->REG_DIODE); + err = nct6775_read_value(data, data->REG_DIODE, &diode); + if (err) + return err; for (i = 0; i < data->temp_fixed_num; i++) { if (!(data->have_temp_fixed & BIT(i))) @@ -3759,241 +3363,24 @@ static inline void nct6775_init_device(struct nct6775_data *data) else /* thermistor */ data->temp_type[i] = 4; } + + return 0; } -static void -nct6775_check_fan_inputs(struct nct6775_data *data, struct nct6775_sio_data *sio_data) +static int add_temp_sensors(struct nct6775_data *data, const u16 *regp, + int *available, int *mask) { - bool fan3pin = false, fan4pin = false, fan4min = false; - bool fan5pin = false, fan6pin = false, fan7pin = false; - bool pwm3pin = false, pwm4pin = false, pwm5pin = false; - bool pwm6pin = false, pwm7pin = false; - - /* Store SIO_REG_ENABLE for use during resume */ - sio_data->sio_select(sio_data, NCT6775_LD_HWM); - data->sio_reg_enable = sio_data->sio_inb(sio_data, SIO_REG_ENABLE); - - /* fan4 and fan5 share some pins with the GPIO and serial flash */ - if (data->kind == nct6775) { - int cr2c = sio_data->sio_inb(sio_data, 0x2c); - - fan3pin = cr2c & BIT(6); - pwm3pin = cr2c & BIT(7); - - /* On NCT6775, fan4 shares pins with the fdc interface */ - fan4pin = !(sio_data->sio_inb(sio_data, 0x2A) & 0x80); - } else if (data->kind == nct6776) { - bool gpok = sio_data->sio_inb(sio_data, 0x27) & 0x80; - const char *board_vendor, *board_name; - - board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); - board_name = dmi_get_system_info(DMI_BOARD_NAME); - - if (board_name && board_vendor && - !strcmp(board_vendor, "ASRock")) { - /* - * Auxiliary fan monitoring is not enabled on ASRock - * Z77 Pro4-M if booted in UEFI Ultra-FastBoot mode. - * Observed with BIOS version 2.00. - */ - if (!strcmp(board_name, "Z77 Pro4-M")) { - if ((data->sio_reg_enable & 0xe0) != 0xe0) { - data->sio_reg_enable |= 0xe0; - sio_data->sio_outb(sio_data, SIO_REG_ENABLE, - data->sio_reg_enable); - } - } - } - - if (data->sio_reg_enable & 0x80) - fan3pin = gpok; - else - fan3pin = !(sio_data->sio_inb(sio_data, 0x24) & 0x40); - - if (data->sio_reg_enable & 0x40) - fan4pin = gpok; - else - fan4pin = sio_data->sio_inb(sio_data, 0x1C) & 0x01; - - if (data->sio_reg_enable & 0x20) - fan5pin = gpok; - else - fan5pin = sio_data->sio_inb(sio_data, 0x1C) & 0x02; - - fan4min = fan4pin; - pwm3pin = fan3pin; - } else if (data->kind == nct6106) { - int cr24 = sio_data->sio_inb(sio_data, 0x24); - - fan3pin = !(cr24 & 0x80); - pwm3pin = cr24 & 0x08; - } else if (data->kind == nct6116) { - int cr1a = sio_data->sio_inb(sio_data, 0x1a); - int cr1b = sio_data->sio_inb(sio_data, 0x1b); - int cr24 = sio_data->sio_inb(sio_data, 0x24); - int cr2a = sio_data->sio_inb(sio_data, 0x2a); - int cr2b = sio_data->sio_inb(sio_data, 0x2b); - int cr2f = sio_data->sio_inb(sio_data, 0x2f); - - fan3pin = !(cr2b & 0x10); - fan4pin = (cr2b & 0x80) || // pin 1(2) - (!(cr2f & 0x10) && (cr1a & 0x04)); // pin 65(66) - fan5pin = (cr2b & 0x80) || // pin 126(127) - (!(cr1b & 0x03) && (cr2a & 0x02)); // pin 94(96) - - pwm3pin = fan3pin && (cr24 & 0x08); - pwm4pin = fan4pin; - pwm5pin = fan5pin; - } else { - /* - * NCT6779D, NCT6791D, NCT6792D, NCT6793D, NCT6795D, NCT6796D, - * NCT6797D, NCT6798D - */ - int cr1a = sio_data->sio_inb(sio_data, 0x1a); - int cr1b = sio_data->sio_inb(sio_data, 0x1b); - int cr1c = sio_data->sio_inb(sio_data, 0x1c); - int cr1d = sio_data->sio_inb(sio_data, 0x1d); - int cr2a = sio_data->sio_inb(sio_data, 0x2a); - int cr2b = sio_data->sio_inb(sio_data, 0x2b); - int cr2d = sio_data->sio_inb(sio_data, 0x2d); - int cr2f = sio_data->sio_inb(sio_data, 0x2f); - bool dsw_en = cr2f & BIT(3); - bool ddr4_en = cr2f & BIT(4); - int cre0; - int creb; - int cred; - - sio_data->sio_select(sio_data, NCT6775_LD_12); - cre0 = sio_data->sio_inb(sio_data, 0xe0); - creb = sio_data->sio_inb(sio_data, 0xeb); - cred = sio_data->sio_inb(sio_data, 0xed); - - fan3pin = !(cr1c & BIT(5)); - fan4pin = !(cr1c & BIT(6)); - fan5pin = !(cr1c & BIT(7)); - - pwm3pin = !(cr1c & BIT(0)); - pwm4pin = !(cr1c & BIT(1)); - pwm5pin = !(cr1c & BIT(2)); - - switch (data->kind) { - case nct6791: - fan6pin = cr2d & BIT(1); - pwm6pin = cr2d & BIT(0); - break; - case nct6792: - fan6pin = !dsw_en && (cr2d & BIT(1)); - pwm6pin = !dsw_en && (cr2d & BIT(0)); - break; - case nct6793: - fan5pin |= cr1b & BIT(5); - fan5pin |= creb & BIT(5); - - fan6pin = !dsw_en && (cr2d & BIT(1)); - fan6pin |= creb & BIT(3); - - pwm5pin |= cr2d & BIT(7); - pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); - - pwm6pin = !dsw_en && (cr2d & BIT(0)); - pwm6pin |= creb & BIT(2); - break; - case nct6795: - fan5pin |= cr1b & BIT(5); - fan5pin |= creb & BIT(5); - - fan6pin = (cr2a & BIT(4)) && - (!dsw_en || (cred & BIT(4))); - fan6pin |= creb & BIT(3); - - pwm5pin |= cr2d & BIT(7); - pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); - - pwm6pin = (cr2a & BIT(3)) && (cred & BIT(2)); - pwm6pin |= creb & BIT(2); - break; - case nct6796: - fan5pin |= cr1b & BIT(5); - fan5pin |= (cre0 & BIT(3)) && !(cr1b & BIT(0)); - fan5pin |= creb & BIT(5); - - fan6pin = (cr2a & BIT(4)) && - (!dsw_en || (cred & BIT(4))); - fan6pin |= creb & BIT(3); - - fan7pin = !(cr2b & BIT(2)); - - pwm5pin |= cr2d & BIT(7); - pwm5pin |= (cre0 & BIT(4)) && !(cr1b & BIT(0)); - pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); - - pwm6pin = (cr2a & BIT(3)) && (cred & BIT(2)); - pwm6pin |= creb & BIT(2); - - pwm7pin = !(cr1d & (BIT(2) | BIT(3))); - break; - case nct6797: - fan5pin |= !ddr4_en && (cr1b & BIT(5)); - fan5pin |= creb & BIT(5); - - fan6pin = cr2a & BIT(4); - fan6pin |= creb & BIT(3); - - fan7pin = cr1a & BIT(1); - - pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); - pwm5pin |= !ddr4_en && (cr2d & BIT(7)); - - pwm6pin = creb & BIT(2); - pwm6pin |= cred & BIT(2); - - pwm7pin = cr1d & BIT(4); - break; - case nct6798: - fan6pin = !(cr1b & BIT(0)) && (cre0 & BIT(3)); - fan6pin |= cr2a & BIT(4); - fan6pin |= creb & BIT(5); - - fan7pin = cr1b & BIT(5); - fan7pin |= !(cr2b & BIT(2)); - fan7pin |= creb & BIT(3); - - pwm6pin = !(cr1b & BIT(0)) && (cre0 & BIT(4)); - pwm6pin |= !(cred & BIT(2)) && (cr2a & BIT(3)); - pwm6pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); - - pwm7pin = !(cr1d & (BIT(2) | BIT(3))); - pwm7pin |= cr2d & BIT(7); - pwm7pin |= creb & BIT(2); - break; - default: /* NCT6779D */ - break; - } - - fan4min = fan4pin; - } - - /* fan 1 and 2 (0x03) are always present */ - data->has_fan = 0x03 | (fan3pin << 2) | (fan4pin << 3) | - (fan5pin << 4) | (fan6pin << 5) | (fan7pin << 6); - data->has_fan_min = 0x03 | (fan3pin << 2) | (fan4min << 3) | - (fan5pin << 4) | (fan6pin << 5) | (fan7pin << 6); - data->has_pwm = 0x03 | (pwm3pin << 2) | (pwm4pin << 3) | - (pwm5pin << 4) | (pwm6pin << 5) | (pwm7pin << 6); -} - -static void add_temp_sensors(struct nct6775_data *data, const u16 *regp, - int *available, int *mask) -{ - int i; - u8 src; + int i, err; + u16 src; for (i = 0; i < data->pwm_num && *available; i++) { int index; if (!regp[i]) continue; - src = data->read_value(data, regp[i]); + err = nct6775_read_value(data, regp[i], &src); + if (err) + return err; src &= 0x1f; if (!src || (*mask & BIT(src))) continue; @@ -4001,58 +3388,36 @@ static void add_temp_sensors(struct nct6775_data *data, const u16 *regp, continue; index = __ffs(*available); - data->write_value(data, data->REG_TEMP_SOURCE[index], src); + err = nct6775_write_value(data, data->REG_TEMP_SOURCE[index], src); + if (err) + return err; *available &= ~BIT(index); *mask |= BIT(src); } + + return 0; } -static int nct6775_probe(struct platform_device *pdev) +int nct6775_probe(struct device *dev, struct nct6775_data *data, + const struct regmap_config *regmapcfg) { - struct device *dev = &pdev->dev; - struct nct6775_sio_data *sio_data = dev_get_platdata(dev); - struct nct6775_data *data; - struct resource *res; int i, s, err = 0; - int src, mask, available; + int mask, available; + u16 src; const u16 *reg_temp, *reg_temp_over, *reg_temp_hyst, *reg_temp_config; const u16 *reg_temp_mon, *reg_temp_alternate, *reg_temp_crit; const u16 *reg_temp_crit_l = NULL, *reg_temp_crit_h = NULL; int num_reg_temp, num_reg_temp_mon, num_reg_tsi_temp; - u8 cr2a; - struct attribute_group *group; struct device *hwmon_dev; struct sensor_template_group tsi_temp_tg; - int num_attr_groups = 0; - if (sio_data->access == access_direct) { - res = platform_get_resource(pdev, IORESOURCE_IO, 0); - if (!devm_request_region(&pdev->dev, res->start, IOREGION_LENGTH, - DRVNAME)) - return -EBUSY; - } - - data = devm_kzalloc(&pdev->dev, sizeof(struct nct6775_data), - GFP_KERNEL); - if (!data) - return -ENOMEM; - - data->kind = sio_data->kind; - data->sio_data = sio_data; - - if (sio_data->access == access_direct) { - data->addr = res->start; - data->read_value = nct6775_read_value; - data->write_value = nct6775_write_value; - } else { - data->read_value = nct6775_wmi_read_value; - data->write_value = nct6775_wmi_write_value; - } + data->regmap = devm_regmap_init(dev, NULL, data, regmapcfg); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); mutex_init(&data->update_lock); data->name = nct6775_device_names[data->kind]; data->bank = 0xff; /* Force initial bank selection */ - platform_set_drvdata(pdev, data); switch (data->kind) { case nct6106: @@ -4596,7 +3961,10 @@ static int nct6775_probe(struct platform_device *pdev) if (reg_temp[i] == 0) continue; - src = data->read_value(data, data->REG_TEMP_SOURCE[i]) & 0x1f; + err = nct6775_read_value(data, data->REG_TEMP_SOURCE[i], &src); + if (err) + return err; + src &= 0x1f; if (!src || (mask & BIT(src))) available |= BIT(i); @@ -4607,8 +3975,12 @@ static int nct6775_probe(struct platform_device *pdev) * Now find unmonitored temperature registers and enable monitoring * if additional monitoring registers are available. */ - add_temp_sensors(data, data->REG_TEMP_SEL, &available, &mask); - add_temp_sensors(data, data->REG_WEIGHT_TEMP_SEL, &available, &mask); + err = add_temp_sensors(data, data->REG_TEMP_SEL, &available, &mask); + if (err) + return err; + err = add_temp_sensors(data, data->REG_WEIGHT_TEMP_SEL, &available, &mask); + if (err) + return err; mask = 0; s = NUM_TEMP_FIXED; /* First dynamic temperature attribute */ @@ -4616,7 +3988,10 @@ static int nct6775_probe(struct platform_device *pdev) if (reg_temp[i] == 0) continue; - src = data->read_value(data, data->REG_TEMP_SOURCE[i]) & 0x1f; + err = nct6775_read_value(data, data->REG_TEMP_SOURCE[i], &src); + if (err) + return err; + src &= 0x1f; if (!src || (mask & BIT(src))) continue; @@ -4676,7 +4051,10 @@ static int nct6775_probe(struct platform_device *pdev) if (reg_temp_mon[i] == 0) continue; - src = data->read_value(data, data->REG_TEMP_SEL[i]) & 0x1f; + err = nct6775_read_value(data, data->REG_TEMP_SEL[i], &src); + if (err) + return err; + src &= 0x1f; if (!src) continue; @@ -4760,525 +4138,68 @@ static int nct6775_probe(struct platform_device *pdev) /* Check which TSIx_TEMP registers are active */ for (i = 0; i < num_reg_tsi_temp; i++) { - if (data->read_value(data, data->REG_TSI_TEMP[i])) + u16 tmp; + + err = nct6775_read_value(data, data->REG_TSI_TEMP[i], &tmp); + if (err) + return err; + if (tmp) data->have_tsi_temp |= BIT(i); } /* Initialize the chip */ - nct6775_init_device(data); - - err = sio_data->sio_enter(sio_data); + err = nct6775_init_device(data); if (err) return err; - cr2a = sio_data->sio_inb(sio_data, 0x2a); - switch (data->kind) { - case nct6775: - data->have_vid = (cr2a & 0x40); - break; - case nct6776: - data->have_vid = (cr2a & 0x60) == 0x40; - break; - case nct6106: - case nct6116: - case nct6779: - case nct6791: - case nct6792: - case nct6793: - case nct6795: - case nct6796: - case nct6797: - case nct6798: - break; + if (data->driver_init) { + err = data->driver_init(data); + if (err) + return err; } - /* - * Read VID value - * We can get the VID input values directly at logical device D 0xe3. - */ - if (data->have_vid) { - sio_data->sio_select(sio_data, NCT6775_LD_VID); - data->vid = sio_data->sio_inb(sio_data, 0xe3); - data->vrm = vid_which_vrm(); - } - - if (fan_debounce) { - u8 tmp; - - sio_data->sio_select(sio_data, NCT6775_LD_HWM); - tmp = sio_data->sio_inb(sio_data, - NCT6775_REG_CR_FAN_DEBOUNCE); - switch (data->kind) { - case nct6106: - case nct6116: - tmp |= 0xe0; - break; - case nct6775: - tmp |= 0x1e; - break; - case nct6776: - case nct6779: - tmp |= 0x3e; - break; - case nct6791: - case nct6792: - case nct6793: - case nct6795: - case nct6796: - case nct6797: - case nct6798: - tmp |= 0x7e; - break; - } - sio_data->sio_outb(sio_data, NCT6775_REG_CR_FAN_DEBOUNCE, - tmp); - dev_info(&pdev->dev, "Enabled fan debounce for chip %s\n", - data->name); - } - - nct6775_check_fan_inputs(data, sio_data); - - sio_data->sio_exit(sio_data); - /* Read fan clock dividers immediately */ - nct6775_init_fan_common(dev, data); + err = nct6775_init_fan_common(dev, data); + if (err) + return err; /* Register sysfs hooks */ - group = nct6775_create_attr_group(dev, &nct6775_pwm_template_group, - data->pwm_num); - if (IS_ERR(group)) - return PTR_ERR(group); + err = nct6775_add_template_attr_group(dev, data, &nct6775_pwm_template_group, + data->pwm_num); + if (err) + return err; - data->groups[num_attr_groups++] = group; + err = nct6775_add_template_attr_group(dev, data, &nct6775_in_template_group, + fls(data->have_in)); + if (err) + return err; - group = nct6775_create_attr_group(dev, &nct6775_in_template_group, - fls(data->have_in)); - if (IS_ERR(group)) - return PTR_ERR(group); + err = nct6775_add_template_attr_group(dev, data, &nct6775_fan_template_group, + fls(data->has_fan)); + if (err) + return err; - data->groups[num_attr_groups++] = group; - - group = nct6775_create_attr_group(dev, &nct6775_fan_template_group, - fls(data->has_fan)); - if (IS_ERR(group)) - return PTR_ERR(group); - - data->groups[num_attr_groups++] = group; - - group = nct6775_create_attr_group(dev, &nct6775_temp_template_group, - fls(data->have_temp)); - if (IS_ERR(group)) - return PTR_ERR(group); - - data->groups[num_attr_groups++] = group; + err = nct6775_add_template_attr_group(dev, data, &nct6775_temp_template_group, + fls(data->have_temp)); + if (err) + return err; if (data->have_tsi_temp) { tsi_temp_tg.templates = nct6775_tsi_temp_template; tsi_temp_tg.is_visible = nct6775_tsi_temp_is_visible; tsi_temp_tg.base = fls(data->have_temp) + 1; - group = nct6775_create_attr_group(dev, &tsi_temp_tg, fls(data->have_tsi_temp)); - if (IS_ERR(group)) - return PTR_ERR(group); - - data->groups[num_attr_groups++] = group; + err = nct6775_add_template_attr_group(dev, data, &tsi_temp_tg, + fls(data->have_tsi_temp)); + if (err) + return err; } - data->groups[num_attr_groups++] = &nct6775_group_other; - hwmon_dev = devm_hwmon_device_register_with_groups(dev, data->name, data, data->groups); return PTR_ERR_OR_ZERO(hwmon_dev); } - -static void nct6791_enable_io_mapping(struct nct6775_sio_data *sio_data) -{ - int val; - - val = sio_data->sio_inb(sio_data, NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE); - if (val & 0x10) { - pr_info("Enabling hardware monitor logical device mappings.\n"); - sio_data->sio_outb(sio_data, NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE, - val & ~0x10); - } -} - -static int __maybe_unused nct6775_suspend(struct device *dev) -{ - struct nct6775_data *data = nct6775_update_device(dev); - - mutex_lock(&data->update_lock); - data->vbat = data->read_value(data, data->REG_VBAT); - if (data->kind == nct6775) { - data->fandiv1 = data->read_value(data, NCT6775_REG_FANDIV1); - data->fandiv2 = data->read_value(data, NCT6775_REG_FANDIV2); - } - mutex_unlock(&data->update_lock); - - return 0; -} - -static int __maybe_unused nct6775_resume(struct device *dev) -{ - struct nct6775_data *data = dev_get_drvdata(dev); - struct nct6775_sio_data *sio_data = dev_get_platdata(dev); - int i, j, err = 0; - u8 reg; - - mutex_lock(&data->update_lock); - data->bank = 0xff; /* Force initial bank selection */ - - err = sio_data->sio_enter(sio_data); - if (err) - goto abort; - - sio_data->sio_select(sio_data, NCT6775_LD_HWM); - reg = sio_data->sio_inb(sio_data, SIO_REG_ENABLE); - if (reg != data->sio_reg_enable) - sio_data->sio_outb(sio_data, SIO_REG_ENABLE, data->sio_reg_enable); - - if (data->kind == nct6791 || data->kind == nct6792 || - data->kind == nct6793 || data->kind == nct6795 || - data->kind == nct6796 || data->kind == nct6797 || - data->kind == nct6798) - nct6791_enable_io_mapping(sio_data); - - sio_data->sio_exit(sio_data); - - /* Restore limits */ - for (i = 0; i < data->in_num; i++) { - if (!(data->have_in & BIT(i))) - continue; - - data->write_value(data, data->REG_IN_MINMAX[0][i], - data->in[i][1]); - data->write_value(data, data->REG_IN_MINMAX[1][i], - data->in[i][2]); - } - - for (i = 0; i < ARRAY_SIZE(data->fan_min); i++) { - if (!(data->has_fan_min & BIT(i))) - continue; - - data->write_value(data, data->REG_FAN_MIN[i], - data->fan_min[i]); - } - - for (i = 0; i < NUM_TEMP; i++) { - if (!(data->have_temp & BIT(i))) - continue; - - for (j = 1; j < ARRAY_SIZE(data->reg_temp); j++) - if (data->reg_temp[j][i]) - nct6775_write_temp(data, data->reg_temp[j][i], - data->temp[j][i]); - } - - /* Restore other settings */ - data->write_value(data, data->REG_VBAT, data->vbat); - if (data->kind == nct6775) { - data->write_value(data, NCT6775_REG_FANDIV1, data->fandiv1); - data->write_value(data, NCT6775_REG_FANDIV2, data->fandiv2); - } - -abort: - /* Force re-reading all values */ - data->valid = false; - mutex_unlock(&data->update_lock); - - return err; -} - -static SIMPLE_DEV_PM_OPS(nct6775_dev_pm_ops, nct6775_suspend, nct6775_resume); - -static struct platform_driver nct6775_driver = { - .driver = { - .name = DRVNAME, - .pm = &nct6775_dev_pm_ops, - }, - .probe = nct6775_probe, -}; - -/* nct6775_find() looks for a '627 in the Super-I/O config space */ -static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data) -{ - u16 val; - int err; - int addr; - - sio_data->access = access_direct; - sio_data->sioreg = sioaddr; - - err = sio_data->sio_enter(sio_data); - if (err) - return err; - - val = (sio_data->sio_inb(sio_data, SIO_REG_DEVID) << 8) | - sio_data->sio_inb(sio_data, SIO_REG_DEVID + 1); - if (force_id && val != 0xffff) - val = force_id; - - switch (val & SIO_ID_MASK) { - case SIO_NCT6106_ID: - sio_data->kind = nct6106; - break; - case SIO_NCT6116_ID: - sio_data->kind = nct6116; - break; - case SIO_NCT6775_ID: - sio_data->kind = nct6775; - break; - case SIO_NCT6776_ID: - sio_data->kind = nct6776; - break; - case SIO_NCT6779_ID: - sio_data->kind = nct6779; - break; - case SIO_NCT6791_ID: - sio_data->kind = nct6791; - break; - case SIO_NCT6792_ID: - sio_data->kind = nct6792; - break; - case SIO_NCT6793_ID: - sio_data->kind = nct6793; - break; - case SIO_NCT6795_ID: - sio_data->kind = nct6795; - break; - case SIO_NCT6796_ID: - sio_data->kind = nct6796; - break; - case SIO_NCT6797_ID: - sio_data->kind = nct6797; - break; - case SIO_NCT6798_ID: - sio_data->kind = nct6798; - break; - default: - if (val != 0xffff) - pr_debug("unsupported chip ID: 0x%04x\n", val); - sio_data->sio_exit(sio_data); - return -ENODEV; - } - - /* We have a known chip, find the HWM I/O address */ - sio_data->sio_select(sio_data, NCT6775_LD_HWM); - val = (sio_data->sio_inb(sio_data, SIO_REG_ADDR) << 8) - | sio_data->sio_inb(sio_data, SIO_REG_ADDR + 1); - addr = val & IOREGION_ALIGNMENT; - if (addr == 0) { - pr_err("Refusing to enable a Super-I/O device with a base I/O port 0\n"); - sio_data->sio_exit(sio_data); - return -ENODEV; - } - - /* Activate logical device if needed */ - val = sio_data->sio_inb(sio_data, SIO_REG_ENABLE); - if (!(val & 0x01)) { - pr_warn("Forcibly enabling Super-I/O. Sensor is probably unusable.\n"); - sio_data->sio_outb(sio_data, SIO_REG_ENABLE, val | 0x01); - } - - if (sio_data->kind == nct6791 || sio_data->kind == nct6792 || - sio_data->kind == nct6793 || sio_data->kind == nct6795 || - sio_data->kind == nct6796 || sio_data->kind == nct6797 || - sio_data->kind == nct6798) - nct6791_enable_io_mapping(sio_data); - - sio_data->sio_exit(sio_data); - pr_info("Found %s or compatible chip at %#x:%#x\n", - nct6775_sio_names[sio_data->kind], sioaddr, addr); - - return addr; -} - -/* - * when Super-I/O functions move to a separate file, the Super-I/O - * bus will manage the lifetime of the device and this module will only keep - * track of the nct6775 driver. But since we use platform_device_alloc(), we - * must keep track of the device - */ -static struct platform_device *pdev[2]; - -static const char * const asus_wmi_boards[] = { - "ProArt X570-CREATOR WIFI", - "Pro B550M-C", - "Pro WS X570-ACE", - "PRIME B360-PLUS", - "PRIME B460-PLUS", - "PRIME B550-PLUS", - "PRIME B550M-A", - "PRIME B550M-A (WI-FI)", - "PRIME X570-P", - "PRIME X570-PRO", - "ROG CROSSHAIR VIII DARK HERO", - "ROG CROSSHAIR VIII FORMULA", - "ROG CROSSHAIR VIII HERO", - "ROG CROSSHAIR VIII IMPACT", - "ROG STRIX B550-A GAMING", - "ROG STRIX B550-E GAMING", - "ROG STRIX B550-F GAMING", - "ROG STRIX B550-F GAMING (WI-FI)", - "ROG STRIX B550-F GAMING WIFI II", - "ROG STRIX B550-I GAMING", - "ROG STRIX B550-XE GAMING (WI-FI)", - "ROG STRIX X570-E GAMING", - "ROG STRIX X570-F GAMING", - "ROG STRIX X570-I GAMING", - "ROG STRIX Z390-E GAMING", - "ROG STRIX Z390-F GAMING", - "ROG STRIX Z390-H GAMING", - "ROG STRIX Z390-I GAMING", - "ROG STRIX Z490-A GAMING", - "ROG STRIX Z490-E GAMING", - "ROG STRIX Z490-F GAMING", - "ROG STRIX Z490-G GAMING", - "ROG STRIX Z490-G GAMING (WI-FI)", - "ROG STRIX Z490-H GAMING", - "ROG STRIX Z490-I GAMING", - "TUF GAMING B550M-PLUS", - "TUF GAMING B550M-PLUS (WI-FI)", - "TUF GAMING B550-PLUS", - "TUF GAMING B550-PRO", - "TUF GAMING X570-PLUS", - "TUF GAMING X570-PLUS (WI-FI)", - "TUF GAMING X570-PRO (WI-FI)", - "TUF GAMING Z490-PLUS", - "TUF GAMING Z490-PLUS (WI-FI)", -}; - -static int __init sensors_nct6775_init(void) -{ - int i, err; - bool found = false; - int address; - struct resource res; - struct nct6775_sio_data sio_data; - int sioaddr[2] = { 0x2e, 0x4e }; - enum sensor_access access = access_direct; - const char *board_vendor, *board_name; - u8 tmp; - - err = platform_driver_register(&nct6775_driver); - if (err) - return err; - - board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); - board_name = dmi_get_system_info(DMI_BOARD_NAME); - - if (board_name && board_vendor && - !strcmp(board_vendor, "ASUSTeK COMPUTER INC.")) { - err = match_string(asus_wmi_boards, ARRAY_SIZE(asus_wmi_boards), - board_name); - if (err >= 0) { - /* if reading chip id via WMI succeeds, use WMI */ - if (!nct6775_asuswmi_read(0, NCT6775_PORT_CHIPID, &tmp) && tmp) { - pr_info("Using Asus WMI to access %#x chip.\n", tmp); - access = access_asuswmi; - } else { - pr_err("Can't read ChipID by Asus WMI.\n"); - } - } - } - - /* - * initialize sio_data->kind and sio_data->sioreg. - * - * when Super-I/O functions move to a separate file, the Super-I/O - * driver will probe 0x2e and 0x4e and auto-detect the presence of a - * nct6775 hardware monitor, and call probe() - */ - for (i = 0; i < ARRAY_SIZE(pdev); i++) { - sio_data.sio_outb = superio_outb; - sio_data.sio_inb = superio_inb; - sio_data.sio_select = superio_select; - sio_data.sio_enter = superio_enter; - sio_data.sio_exit = superio_exit; - - address = nct6775_find(sioaddr[i], &sio_data); - if (address <= 0) - continue; - - found = true; - - sio_data.access = access; - - if (access == access_asuswmi) { - sio_data.sio_outb = superio_wmi_outb; - sio_data.sio_inb = superio_wmi_inb; - sio_data.sio_select = superio_wmi_select; - sio_data.sio_enter = superio_wmi_enter; - sio_data.sio_exit = superio_wmi_exit; - } - - pdev[i] = platform_device_alloc(DRVNAME, address); - if (!pdev[i]) { - err = -ENOMEM; - goto exit_device_unregister; - } - - err = platform_device_add_data(pdev[i], &sio_data, - sizeof(struct nct6775_sio_data)); - if (err) - goto exit_device_put; - - if (sio_data.access == access_direct) { - memset(&res, 0, sizeof(res)); - res.name = DRVNAME; - res.start = address + IOREGION_OFFSET; - res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1; - res.flags = IORESOURCE_IO; - - err = acpi_check_resource_conflict(&res); - if (err) { - platform_device_put(pdev[i]); - pdev[i] = NULL; - continue; - } - - err = platform_device_add_resources(pdev[i], &res, 1); - if (err) - goto exit_device_put; - } - - /* platform_device_add calls probe() */ - err = platform_device_add(pdev[i]); - if (err) - goto exit_device_put; - } - if (!found) { - err = -ENODEV; - goto exit_unregister; - } - - return 0; - -exit_device_put: - platform_device_put(pdev[i]); -exit_device_unregister: - while (--i >= 0) { - if (pdev[i]) - platform_device_unregister(pdev[i]); - } -exit_unregister: - platform_driver_unregister(&nct6775_driver); - return err; -} - -static void __exit sensors_nct6775_exit(void) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(pdev); i++) { - if (pdev[i]) - platform_device_unregister(pdev[i]); - } - platform_driver_unregister(&nct6775_driver); -} +EXPORT_SYMBOL_GPL(nct6775_probe); MODULE_AUTHOR("Guenter Roeck "); -MODULE_DESCRIPTION("Driver for NCT6775F and compatible chips"); +MODULE_DESCRIPTION("Core driver for NCT6775F and compatible chips"); MODULE_LICENSE("GPL"); - -module_init(sensors_nct6775_init); -module_exit(sensors_nct6775_exit); diff --git a/drivers/hwmon/nct6775-i2c.c b/drivers/hwmon/nct6775-i2c.c new file mode 100644 index 000000000000..e1bcd1146191 --- /dev/null +++ b/drivers/hwmon/nct6775-i2c.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * nct6775-i2c - I2C driver for the hardware monitoring functionality of + * Nuvoton NCT677x Super-I/O chips + * + * Copyright (C) 2022 Zev Weiss + * + * This driver interacts with the chip via it's "back door" i2c interface, as + * is often exposed to a BMC. Because the host may still be operating the + * chip via the ("front door") LPC interface, this driver cannot assume that + * it actually has full control of the chip, and in particular must avoid + * making any changes that could confuse the host's LPC usage of it. It thus + * operates in a strictly read-only fashion, with the only exception being the + * bank-select register (which seems, thankfully, to be replicated for the i2c + * interface so it doesn't affect the LPC interface). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "nct6775.h" + +static int nct6775_i2c_read(void *ctx, unsigned int reg, unsigned int *val) +{ + int ret; + u32 tmp; + u8 bank = reg >> 8; + struct nct6775_data *data = ctx; + struct i2c_client *client = data->driver_data; + + if (bank != data->bank) { + ret = i2c_smbus_write_byte_data(client, NCT6775_REG_BANK, bank); + if (ret) + return ret; + data->bank = bank; + } + + ret = i2c_smbus_read_byte_data(client, reg & 0xff); + if (ret < 0) + return ret; + tmp = ret; + + if (nct6775_reg_is_word_sized(data, reg)) { + ret = i2c_smbus_read_byte_data(client, (reg & 0xff) + 1); + if (ret < 0) + return ret; + tmp = (tmp << 8) | ret; + } + + *val = tmp; + return 0; +} + +/* + * The write operation is a dummy so as not to disturb anything being done + * with the chip via LPC. + */ +static int nct6775_i2c_write(void *ctx, unsigned int reg, unsigned int value) +{ + struct nct6775_data *data = ctx; + struct i2c_client *client = data->driver_data; + + dev_dbg(&client->dev, "skipping attempted write: %02x -> %03x\n", value, reg); + + /* + * This is a lie, but writing anything but the bank-select register is + * something this driver shouldn't be doing. + */ + return 0; +} + +static const struct of_device_id __maybe_unused nct6775_i2c_of_match[] = { + { .compatible = "nuvoton,nct6106", .data = (void *)nct6106, }, + { .compatible = "nuvoton,nct6116", .data = (void *)nct6116, }, + { .compatible = "nuvoton,nct6775", .data = (void *)nct6775, }, + { .compatible = "nuvoton,nct6776", .data = (void *)nct6776, }, + { .compatible = "nuvoton,nct6779", .data = (void *)nct6779, }, + { .compatible = "nuvoton,nct6791", .data = (void *)nct6791, }, + { .compatible = "nuvoton,nct6792", .data = (void *)nct6792, }, + { .compatible = "nuvoton,nct6793", .data = (void *)nct6793, }, + { .compatible = "nuvoton,nct6795", .data = (void *)nct6795, }, + { .compatible = "nuvoton,nct6796", .data = (void *)nct6796, }, + { .compatible = "nuvoton,nct6797", .data = (void *)nct6797, }, + { .compatible = "nuvoton,nct6798", .data = (void *)nct6798, }, + { }, +}; +MODULE_DEVICE_TABLE(of, nct6775_i2c_of_match); + +static const struct i2c_device_id nct6775_i2c_id[] = { + { "nct6106", nct6106 }, + { "nct6116", nct6116 }, + { "nct6775", nct6775 }, + { "nct6776", nct6776 }, + { "nct6779", nct6779 }, + { "nct6791", nct6791 }, + { "nct6792", nct6792 }, + { "nct6793", nct6793 }, + { "nct6795", nct6795 }, + { "nct6796", nct6796 }, + { "nct6797", nct6797 }, + { "nct6798", nct6798 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nct6775_i2c_id); + +static int nct6775_i2c_probe_init(struct nct6775_data *data) +{ + u32 tsi_channel_mask; + struct i2c_client *client = data->driver_data; + + /* + * The i2c interface doesn't provide access to the control registers + * needed to determine the presence of other fans, but fans 1 and 2 + * are (in principle) always there. + * + * In practice this is perhaps a little silly, because the system + * using this driver is mostly likely a BMC, and hence probably has + * totally separate fan tachs & pwms of its own that are actually + * controlling/monitoring the fans -- these are thus unlikely to be + * doing anything actually useful. + */ + data->has_fan = 0x03; + data->has_fan_min = 0x03; + data->has_pwm = 0x03; + + /* + * Because on a BMC this driver may be bound very shortly after power + * is first applied to the device, the automatic TSI channel detection + * in nct6775_probe() (which has already been run at this point) may + * not find anything if a channel hasn't yet produced a temperature + * reading. Augment whatever was found via autodetection (if + * anything) with the channels DT says should be active. + */ + if (!of_property_read_u32(client->dev.of_node, "nuvoton,tsi-channel-mask", + &tsi_channel_mask)) + data->have_tsi_temp |= tsi_channel_mask & GENMASK(NUM_TSI_TEMP - 1, 0); + + return 0; +} + +static const struct regmap_config nct6775_i2c_regmap_config = { + .reg_bits = 16, + .val_bits = 16, + .reg_read = nct6775_i2c_read, + .reg_write = nct6775_i2c_write, +}; + +static int nct6775_i2c_probe(struct i2c_client *client) +{ + struct nct6775_data *data; + const struct of_device_id *of_id; + const struct i2c_device_id *i2c_id; + struct device *dev = &client->dev; + + of_id = of_match_device(nct6775_i2c_of_match, dev); + i2c_id = i2c_match_id(nct6775_i2c_id, client); + + if (of_id && (unsigned long)of_id->data != i2c_id->driver_data) + dev_notice(dev, "Device mismatch: %s in device tree, %s detected\n", + of_id->name, i2c_id->name); + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->kind = i2c_id->driver_data; + + data->read_only = true; + data->driver_data = client; + data->driver_init = nct6775_i2c_probe_init; + + return nct6775_probe(dev, data, &nct6775_i2c_regmap_config); +} + +static struct i2c_driver nct6775_i2c_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "nct6775-i2c", + .of_match_table = of_match_ptr(nct6775_i2c_of_match), + }, + .probe_new = nct6775_i2c_probe, + .id_table = nct6775_i2c_id, +}; + +module_i2c_driver(nct6775_i2c_driver); + +MODULE_AUTHOR("Zev Weiss "); +MODULE_DESCRIPTION("I2C driver for NCT6775F and compatible chips"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(HWMON_NCT6775); diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c new file mode 100644 index 000000000000..6d46c9401898 --- /dev/null +++ b/drivers/hwmon/nct6775-platform.c @@ -0,0 +1,1229 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * nct6775 - Platform driver for the hardware monitoring + * functionality of Nuvoton NCT677x Super-I/O chips + * + * Copyright (C) 2012 Guenter Roeck + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nct6775.h" + +enum sensor_access { access_direct, access_asuswmi }; + +static const char * const nct6775_sio_names[] __initconst = { + "NCT6106D", + "NCT6116D", + "NCT6775F", + "NCT6776D/F", + "NCT6779D", + "NCT6791D", + "NCT6792D", + "NCT6793D", + "NCT6795D", + "NCT6796D", + "NCT6797D", + "NCT6798D", +}; + +static unsigned short force_id; +module_param(force_id, ushort, 0); +MODULE_PARM_DESC(force_id, "Override the detected device ID"); + +static unsigned short fan_debounce; +module_param(fan_debounce, ushort, 0); +MODULE_PARM_DESC(fan_debounce, "Enable debouncing for fan RPM signal"); + +#define DRVNAME "nct6775" + +#define NCT6775_PORT_CHIPID 0x58 + +/* + * ISA constants + */ + +#define IOREGION_ALIGNMENT (~7) +#define IOREGION_OFFSET 5 +#define IOREGION_LENGTH 2 +#define ADDR_REG_OFFSET 0 +#define DATA_REG_OFFSET 1 + +/* + * Super-I/O constants and functions + */ + +#define NCT6775_LD_ACPI 0x0a +#define NCT6775_LD_HWM 0x0b +#define NCT6775_LD_VID 0x0d +#define NCT6775_LD_12 0x12 + +#define SIO_REG_LDSEL 0x07 /* Logical device select */ +#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ +#define SIO_REG_ENABLE 0x30 /* Logical device enable */ +#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ + +#define SIO_NCT6106_ID 0xc450 +#define SIO_NCT6116_ID 0xd280 +#define SIO_NCT6775_ID 0xb470 +#define SIO_NCT6776_ID 0xc330 +#define SIO_NCT6779_ID 0xc560 +#define SIO_NCT6791_ID 0xc800 +#define SIO_NCT6792_ID 0xc910 +#define SIO_NCT6793_ID 0xd120 +#define SIO_NCT6795_ID 0xd350 +#define SIO_NCT6796_ID 0xd420 +#define SIO_NCT6797_ID 0xd450 +#define SIO_NCT6798_ID 0xd428 +#define SIO_ID_MASK 0xFFF8 + +/* + * Control registers + */ +#define NCT6775_REG_CR_FAN_DEBOUNCE 0xf0 + +struct nct6775_sio_data { + int sioreg; + int ld; + enum kinds kind; + enum sensor_access access; + + /* superio_() callbacks */ + void (*sio_outb)(struct nct6775_sio_data *sio_data, int reg, int val); + int (*sio_inb)(struct nct6775_sio_data *sio_data, int reg); + void (*sio_select)(struct nct6775_sio_data *sio_data, int ld); + int (*sio_enter)(struct nct6775_sio_data *sio_data); + void (*sio_exit)(struct nct6775_sio_data *sio_data); +}; + +#define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66" +#define ASUSWMI_METHODID_RSIO 0x5253494F +#define ASUSWMI_METHODID_WSIO 0x5753494F +#define ASUSWMI_METHODID_RHWM 0x5248574D +#define ASUSWMI_METHODID_WHWM 0x5748574D +#define ASUSWMI_UNSUPPORTED_METHOD 0xFFFFFFFE + +static int nct6775_asuswmi_evaluate_method(u32 method_id, u8 bank, u8 reg, u8 val, u32 *retval) +{ +#if IS_ENABLED(CONFIG_ACPI_WMI) + u32 args = bank | (reg << 8) | (val << 16); + struct acpi_buffer input = { (acpi_size) sizeof(args), &args }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + union acpi_object *obj; + u32 tmp = ASUSWMI_UNSUPPORTED_METHOD; + + status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, 0, + method_id, &input, &output); + + if (ACPI_FAILURE(status)) + return -EIO; + + obj = output.pointer; + if (obj && obj->type == ACPI_TYPE_INTEGER) + tmp = obj->integer.value; + + if (retval) + *retval = tmp; + + kfree(obj); + + if (tmp == ASUSWMI_UNSUPPORTED_METHOD) + return -ENODEV; + return 0; +#else + return -EOPNOTSUPP; +#endif +} + +static inline int nct6775_asuswmi_write(u8 bank, u8 reg, u8 val) +{ + return nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_WHWM, bank, + reg, val, NULL); +} + +static inline int nct6775_asuswmi_read(u8 bank, u8 reg, u8 *val) +{ + u32 ret, tmp = 0; + + ret = nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_RHWM, bank, + reg, 0, &tmp); + *val = tmp; + return ret; +} + +static int superio_wmi_inb(struct nct6775_sio_data *sio_data, int reg) +{ + int tmp = 0; + + nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_RSIO, sio_data->ld, + reg, 0, &tmp); + return tmp; +} + +static void superio_wmi_outb(struct nct6775_sio_data *sio_data, int reg, int val) +{ + nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_WSIO, sio_data->ld, + reg, val, NULL); +} + +static void superio_wmi_select(struct nct6775_sio_data *sio_data, int ld) +{ + sio_data->ld = ld; +} + +static int superio_wmi_enter(struct nct6775_sio_data *sio_data) +{ + return 0; +} + +static void superio_wmi_exit(struct nct6775_sio_data *sio_data) +{ +} + +static void superio_outb(struct nct6775_sio_data *sio_data, int reg, int val) +{ + int ioreg = sio_data->sioreg; + + outb(reg, ioreg); + outb(val, ioreg + 1); +} + +static int superio_inb(struct nct6775_sio_data *sio_data, int reg) +{ + int ioreg = sio_data->sioreg; + + outb(reg, ioreg); + return inb(ioreg + 1); +} + +static void superio_select(struct nct6775_sio_data *sio_data, int ld) +{ + int ioreg = sio_data->sioreg; + + outb(SIO_REG_LDSEL, ioreg); + outb(ld, ioreg + 1); +} + +static int superio_enter(struct nct6775_sio_data *sio_data) +{ + int ioreg = sio_data->sioreg; + + /* + * Try to reserve and for exclusive access. + */ + if (!request_muxed_region(ioreg, 2, DRVNAME)) + return -EBUSY; + + outb(0x87, ioreg); + outb(0x87, ioreg); + + return 0; +} + +static void superio_exit(struct nct6775_sio_data *sio_data) +{ + int ioreg = sio_data->sioreg; + + outb(0xaa, ioreg); + outb(0x02, ioreg); + outb(0x02, ioreg + 1); + release_region(ioreg, 2); +} + +static inline void nct6775_wmi_set_bank(struct nct6775_data *data, u16 reg) +{ + u8 bank = reg >> 8; + + data->bank = bank; +} + +static int nct6775_wmi_reg_read(void *ctx, unsigned int reg, unsigned int *val) +{ + struct nct6775_data *data = ctx; + int err, word_sized = nct6775_reg_is_word_sized(data, reg); + u8 tmp = 0; + u16 res; + + nct6775_wmi_set_bank(data, reg); + + err = nct6775_asuswmi_read(data->bank, reg & 0xff, &tmp); + if (err) + return err; + + res = tmp; + if (word_sized) { + err = nct6775_asuswmi_read(data->bank, (reg & 0xff) + 1, &tmp); + if (err) + return err; + + res = (res << 8) + tmp; + } + *val = res; + return 0; +} + +static int nct6775_wmi_reg_write(void *ctx, unsigned int reg, unsigned int value) +{ + struct nct6775_data *data = ctx; + int res, word_sized = nct6775_reg_is_word_sized(data, reg); + + nct6775_wmi_set_bank(data, reg); + + if (word_sized) { + res = nct6775_asuswmi_write(data->bank, reg & 0xff, value >> 8); + if (res) + return res; + + res = nct6775_asuswmi_write(data->bank, (reg & 0xff) + 1, value); + } else { + res = nct6775_asuswmi_write(data->bank, reg & 0xff, value); + } + + return res; +} + +/* + * On older chips, only registers 0x50-0x5f are banked. + * On more recent chips, all registers are banked. + * Assume that is the case and set the bank number for each access. + * Cache the bank number so it only needs to be set if it changes. + */ +static inline void nct6775_set_bank(struct nct6775_data *data, u16 reg) +{ + u8 bank = reg >> 8; + + if (data->bank != bank) { + outb_p(NCT6775_REG_BANK, data->addr + ADDR_REG_OFFSET); + outb_p(bank, data->addr + DATA_REG_OFFSET); + data->bank = bank; + } +} + +static int nct6775_reg_read(void *ctx, unsigned int reg, unsigned int *val) +{ + struct nct6775_data *data = ctx; + int word_sized = nct6775_reg_is_word_sized(data, reg); + + nct6775_set_bank(data, reg); + outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); + *val = inb_p(data->addr + DATA_REG_OFFSET); + if (word_sized) { + outb_p((reg & 0xff) + 1, + data->addr + ADDR_REG_OFFSET); + *val = (*val << 8) + inb_p(data->addr + DATA_REG_OFFSET); + } + return 0; +} + +static int nct6775_reg_write(void *ctx, unsigned int reg, unsigned int value) +{ + struct nct6775_data *data = ctx; + int word_sized = nct6775_reg_is_word_sized(data, reg); + + nct6775_set_bank(data, reg); + outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); + if (word_sized) { + outb_p(value >> 8, data->addr + DATA_REG_OFFSET); + outb_p((reg & 0xff) + 1, + data->addr + ADDR_REG_OFFSET); + } + outb_p(value & 0xff, data->addr + DATA_REG_OFFSET); + return 0; +} + +static void nct6791_enable_io_mapping(struct nct6775_sio_data *sio_data) +{ + int val; + + val = sio_data->sio_inb(sio_data, NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE); + if (val & 0x10) { + pr_info("Enabling hardware monitor logical device mappings.\n"); + sio_data->sio_outb(sio_data, NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE, + val & ~0x10); + } +} + +static int __maybe_unused nct6775_suspend(struct device *dev) +{ + int err; + u16 tmp; + struct nct6775_data *data = dev_get_drvdata(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + err = nct6775_read_value(data, data->REG_VBAT, &tmp); + if (err) + goto out; + data->vbat = tmp; + if (data->kind == nct6775) { + err = nct6775_read_value(data, NCT6775_REG_FANDIV1, &tmp); + if (err) + goto out; + data->fandiv1 = tmp; + + err = nct6775_read_value(data, NCT6775_REG_FANDIV2, &tmp); + if (err) + goto out; + data->fandiv2 = tmp; + } +out: + mutex_unlock(&data->update_lock); + + return err; +} + +static int __maybe_unused nct6775_resume(struct device *dev) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct nct6775_sio_data *sio_data = dev_get_platdata(dev); + int i, j, err = 0; + u8 reg; + + mutex_lock(&data->update_lock); + data->bank = 0xff; /* Force initial bank selection */ + + err = sio_data->sio_enter(sio_data); + if (err) + goto abort; + + sio_data->sio_select(sio_data, NCT6775_LD_HWM); + reg = sio_data->sio_inb(sio_data, SIO_REG_ENABLE); + if (reg != data->sio_reg_enable) + sio_data->sio_outb(sio_data, SIO_REG_ENABLE, data->sio_reg_enable); + + if (data->kind == nct6791 || data->kind == nct6792 || + data->kind == nct6793 || data->kind == nct6795 || + data->kind == nct6796 || data->kind == nct6797 || + data->kind == nct6798) + nct6791_enable_io_mapping(sio_data); + + sio_data->sio_exit(sio_data); + + /* Restore limits */ + for (i = 0; i < data->in_num; i++) { + if (!(data->have_in & BIT(i))) + continue; + + err = nct6775_write_value(data, data->REG_IN_MINMAX[0][i], data->in[i][1]); + if (err) + goto abort; + err = nct6775_write_value(data, data->REG_IN_MINMAX[1][i], data->in[i][2]); + if (err) + goto abort; + } + + for (i = 0; i < ARRAY_SIZE(data->fan_min); i++) { + if (!(data->has_fan_min & BIT(i))) + continue; + + err = nct6775_write_value(data, data->REG_FAN_MIN[i], data->fan_min[i]); + if (err) + goto abort; + } + + for (i = 0; i < NUM_TEMP; i++) { + if (!(data->have_temp & BIT(i))) + continue; + + for (j = 1; j < ARRAY_SIZE(data->reg_temp); j++) + if (data->reg_temp[j][i]) { + err = nct6775_write_temp(data, data->reg_temp[j][i], + data->temp[j][i]); + if (err) + goto abort; + } + } + + /* Restore other settings */ + err = nct6775_write_value(data, data->REG_VBAT, data->vbat); + if (err) + goto abort; + if (data->kind == nct6775) { + err = nct6775_write_value(data, NCT6775_REG_FANDIV1, data->fandiv1); + if (err) + goto abort; + err = nct6775_write_value(data, NCT6775_REG_FANDIV2, data->fandiv2); + } + +abort: + /* Force re-reading all values */ + data->valid = false; + mutex_unlock(&data->update_lock); + + return err; +} + +static SIMPLE_DEV_PM_OPS(nct6775_dev_pm_ops, nct6775_suspend, nct6775_resume); + +static void +nct6775_check_fan_inputs(struct nct6775_data *data, struct nct6775_sio_data *sio_data) +{ + bool fan3pin = false, fan4pin = false, fan4min = false; + bool fan5pin = false, fan6pin = false, fan7pin = false; + bool pwm3pin = false, pwm4pin = false, pwm5pin = false; + bool pwm6pin = false, pwm7pin = false; + + /* Store SIO_REG_ENABLE for use during resume */ + sio_data->sio_select(sio_data, NCT6775_LD_HWM); + data->sio_reg_enable = sio_data->sio_inb(sio_data, SIO_REG_ENABLE); + + /* fan4 and fan5 share some pins with the GPIO and serial flash */ + if (data->kind == nct6775) { + int cr2c = sio_data->sio_inb(sio_data, 0x2c); + + fan3pin = cr2c & BIT(6); + pwm3pin = cr2c & BIT(7); + + /* On NCT6775, fan4 shares pins with the fdc interface */ + fan4pin = !(sio_data->sio_inb(sio_data, 0x2A) & 0x80); + } else if (data->kind == nct6776) { + bool gpok = sio_data->sio_inb(sio_data, 0x27) & 0x80; + const char *board_vendor, *board_name; + + board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + board_name = dmi_get_system_info(DMI_BOARD_NAME); + + if (board_name && board_vendor && + !strcmp(board_vendor, "ASRock")) { + /* + * Auxiliary fan monitoring is not enabled on ASRock + * Z77 Pro4-M if booted in UEFI Ultra-FastBoot mode. + * Observed with BIOS version 2.00. + */ + if (!strcmp(board_name, "Z77 Pro4-M")) { + if ((data->sio_reg_enable & 0xe0) != 0xe0) { + data->sio_reg_enable |= 0xe0; + sio_data->sio_outb(sio_data, SIO_REG_ENABLE, + data->sio_reg_enable); + } + } + } + + if (data->sio_reg_enable & 0x80) + fan3pin = gpok; + else + fan3pin = !(sio_data->sio_inb(sio_data, 0x24) & 0x40); + + if (data->sio_reg_enable & 0x40) + fan4pin = gpok; + else + fan4pin = sio_data->sio_inb(sio_data, 0x1C) & 0x01; + + if (data->sio_reg_enable & 0x20) + fan5pin = gpok; + else + fan5pin = sio_data->sio_inb(sio_data, 0x1C) & 0x02; + + fan4min = fan4pin; + pwm3pin = fan3pin; + } else if (data->kind == nct6106) { + int cr24 = sio_data->sio_inb(sio_data, 0x24); + + fan3pin = !(cr24 & 0x80); + pwm3pin = cr24 & 0x08; + } else if (data->kind == nct6116) { + int cr1a = sio_data->sio_inb(sio_data, 0x1a); + int cr1b = sio_data->sio_inb(sio_data, 0x1b); + int cr24 = sio_data->sio_inb(sio_data, 0x24); + int cr2a = sio_data->sio_inb(sio_data, 0x2a); + int cr2b = sio_data->sio_inb(sio_data, 0x2b); + int cr2f = sio_data->sio_inb(sio_data, 0x2f); + + fan3pin = !(cr2b & 0x10); + fan4pin = (cr2b & 0x80) || // pin 1(2) + (!(cr2f & 0x10) && (cr1a & 0x04)); // pin 65(66) + fan5pin = (cr2b & 0x80) || // pin 126(127) + (!(cr1b & 0x03) && (cr2a & 0x02)); // pin 94(96) + + pwm3pin = fan3pin && (cr24 & 0x08); + pwm4pin = fan4pin; + pwm5pin = fan5pin; + } else { + /* + * NCT6779D, NCT6791D, NCT6792D, NCT6793D, NCT6795D, NCT6796D, + * NCT6797D, NCT6798D + */ + int cr1a = sio_data->sio_inb(sio_data, 0x1a); + int cr1b = sio_data->sio_inb(sio_data, 0x1b); + int cr1c = sio_data->sio_inb(sio_data, 0x1c); + int cr1d = sio_data->sio_inb(sio_data, 0x1d); + int cr2a = sio_data->sio_inb(sio_data, 0x2a); + int cr2b = sio_data->sio_inb(sio_data, 0x2b); + int cr2d = sio_data->sio_inb(sio_data, 0x2d); + int cr2f = sio_data->sio_inb(sio_data, 0x2f); + bool dsw_en = cr2f & BIT(3); + bool ddr4_en = cr2f & BIT(4); + int cre0; + int creb; + int cred; + + sio_data->sio_select(sio_data, NCT6775_LD_12); + cre0 = sio_data->sio_inb(sio_data, 0xe0); + creb = sio_data->sio_inb(sio_data, 0xeb); + cred = sio_data->sio_inb(sio_data, 0xed); + + fan3pin = !(cr1c & BIT(5)); + fan4pin = !(cr1c & BIT(6)); + fan5pin = !(cr1c & BIT(7)); + + pwm3pin = !(cr1c & BIT(0)); + pwm4pin = !(cr1c & BIT(1)); + pwm5pin = !(cr1c & BIT(2)); + + switch (data->kind) { + case nct6791: + fan6pin = cr2d & BIT(1); + pwm6pin = cr2d & BIT(0); + break; + case nct6792: + fan6pin = !dsw_en && (cr2d & BIT(1)); + pwm6pin = !dsw_en && (cr2d & BIT(0)); + break; + case nct6793: + fan5pin |= cr1b & BIT(5); + fan5pin |= creb & BIT(5); + + fan6pin = !dsw_en && (cr2d & BIT(1)); + fan6pin |= creb & BIT(3); + + pwm5pin |= cr2d & BIT(7); + pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); + + pwm6pin = !dsw_en && (cr2d & BIT(0)); + pwm6pin |= creb & BIT(2); + break; + case nct6795: + fan5pin |= cr1b & BIT(5); + fan5pin |= creb & BIT(5); + + fan6pin = (cr2a & BIT(4)) && + (!dsw_en || (cred & BIT(4))); + fan6pin |= creb & BIT(3); + + pwm5pin |= cr2d & BIT(7); + pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); + + pwm6pin = (cr2a & BIT(3)) && (cred & BIT(2)); + pwm6pin |= creb & BIT(2); + break; + case nct6796: + fan5pin |= cr1b & BIT(5); + fan5pin |= (cre0 & BIT(3)) && !(cr1b & BIT(0)); + fan5pin |= creb & BIT(5); + + fan6pin = (cr2a & BIT(4)) && + (!dsw_en || (cred & BIT(4))); + fan6pin |= creb & BIT(3); + + fan7pin = !(cr2b & BIT(2)); + + pwm5pin |= cr2d & BIT(7); + pwm5pin |= (cre0 & BIT(4)) && !(cr1b & BIT(0)); + pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); + + pwm6pin = (cr2a & BIT(3)) && (cred & BIT(2)); + pwm6pin |= creb & BIT(2); + + pwm7pin = !(cr1d & (BIT(2) | BIT(3))); + break; + case nct6797: + fan5pin |= !ddr4_en && (cr1b & BIT(5)); + fan5pin |= creb & BIT(5); + + fan6pin = cr2a & BIT(4); + fan6pin |= creb & BIT(3); + + fan7pin = cr1a & BIT(1); + + pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); + pwm5pin |= !ddr4_en && (cr2d & BIT(7)); + + pwm6pin = creb & BIT(2); + pwm6pin |= cred & BIT(2); + + pwm7pin = cr1d & BIT(4); + break; + case nct6798: + fan6pin = !(cr1b & BIT(0)) && (cre0 & BIT(3)); + fan6pin |= cr2a & BIT(4); + fan6pin |= creb & BIT(5); + + fan7pin = cr1b & BIT(5); + fan7pin |= !(cr2b & BIT(2)); + fan7pin |= creb & BIT(3); + + pwm6pin = !(cr1b & BIT(0)) && (cre0 & BIT(4)); + pwm6pin |= !(cred & BIT(2)) && (cr2a & BIT(3)); + pwm6pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); + + pwm7pin = !(cr1d & (BIT(2) | BIT(3))); + pwm7pin |= cr2d & BIT(7); + pwm7pin |= creb & BIT(2); + break; + default: /* NCT6779D */ + break; + } + + fan4min = fan4pin; + } + + /* fan 1 and 2 (0x03) are always present */ + data->has_fan = 0x03 | (fan3pin << 2) | (fan4pin << 3) | + (fan5pin << 4) | (fan6pin << 5) | (fan7pin << 6); + data->has_fan_min = 0x03 | (fan3pin << 2) | (fan4min << 3) | + (fan5pin << 4) | (fan6pin << 5) | (fan7pin << 6); + data->has_pwm = 0x03 | (pwm3pin << 2) | (pwm4pin << 3) | + (pwm5pin << 4) | (pwm6pin << 5) | (pwm7pin << 6); +} + +static ssize_t +cpu0_vid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); +} + +static DEVICE_ATTR_RO(cpu0_vid); + +/* Case open detection */ + +static const u8 NCT6775_REG_CR_CASEOPEN_CLR[] = { 0xe6, 0xee }; +static const u8 NCT6775_CR_CASEOPEN_CLR_MASK[] = { 0x20, 0x01 }; + +static ssize_t +clear_caseopen(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct nct6775_sio_data *sio_data = data->driver_data; + int nr = to_sensor_dev_attr(attr)->index - INTRUSION_ALARM_BASE; + unsigned long val; + u8 reg; + int ret; + + if (kstrtoul(buf, 10, &val) || val != 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + + /* + * Use CR registers to clear caseopen status. + * The CR registers are the same for all chips, and not all chips + * support clearing the caseopen status through "regular" registers. + */ + ret = sio_data->sio_enter(sio_data); + if (ret) { + count = ret; + goto error; + } + + sio_data->sio_select(sio_data, NCT6775_LD_ACPI); + reg = sio_data->sio_inb(sio_data, NCT6775_REG_CR_CASEOPEN_CLR[nr]); + reg |= NCT6775_CR_CASEOPEN_CLR_MASK[nr]; + sio_data->sio_outb(sio_data, NCT6775_REG_CR_CASEOPEN_CLR[nr], reg); + reg &= ~NCT6775_CR_CASEOPEN_CLR_MASK[nr]; + sio_data->sio_outb(sio_data, NCT6775_REG_CR_CASEOPEN_CLR[nr], reg); + sio_data->sio_exit(sio_data); + + data->valid = false; /* Force cache refresh */ +error: + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(intrusion0_alarm, 0644, nct6775_show_alarm, + clear_caseopen, INTRUSION_ALARM_BASE); +static SENSOR_DEVICE_ATTR(intrusion1_alarm, 0644, nct6775_show_alarm, + clear_caseopen, INTRUSION_ALARM_BASE + 1); +static SENSOR_DEVICE_ATTR(intrusion0_beep, 0644, nct6775_show_beep, + nct6775_store_beep, INTRUSION_ALARM_BASE); +static SENSOR_DEVICE_ATTR(intrusion1_beep, 0644, nct6775_show_beep, + nct6775_store_beep, INTRUSION_ALARM_BASE + 1); +static SENSOR_DEVICE_ATTR(beep_enable, 0644, nct6775_show_beep, + nct6775_store_beep, BEEP_ENABLE_BASE); + +static umode_t nct6775_other_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct nct6775_data *data = dev_get_drvdata(dev); + + if (index == 0 && !data->have_vid) + return 0; + + if (index == 1 || index == 2) { + if (data->ALARM_BITS[INTRUSION_ALARM_BASE + index - 1] < 0) + return 0; + } + + if (index == 3 || index == 4) { + if (data->BEEP_BITS[INTRUSION_ALARM_BASE + index - 3] < 0) + return 0; + } + + return nct6775_attr_mode(data, attr); +} + +/* + * nct6775_other_is_visible uses the index into the following array + * to determine if attributes should be created or not. + * Any change in order or content must be matched. + */ +static struct attribute *nct6775_attributes_other[] = { + &dev_attr_cpu0_vid.attr, /* 0 */ + &sensor_dev_attr_intrusion0_alarm.dev_attr.attr, /* 1 */ + &sensor_dev_attr_intrusion1_alarm.dev_attr.attr, /* 2 */ + &sensor_dev_attr_intrusion0_beep.dev_attr.attr, /* 3 */ + &sensor_dev_attr_intrusion1_beep.dev_attr.attr, /* 4 */ + &sensor_dev_attr_beep_enable.dev_attr.attr, /* 5 */ + + NULL +}; + +static const struct attribute_group nct6775_group_other = { + .attrs = nct6775_attributes_other, + .is_visible = nct6775_other_is_visible, +}; + +static int nct6775_platform_probe_init(struct nct6775_data *data) +{ + int err; + u8 cr2a; + struct nct6775_sio_data *sio_data = data->driver_data; + + err = sio_data->sio_enter(sio_data); + if (err) + return err; + + cr2a = sio_data->sio_inb(sio_data, 0x2a); + switch (data->kind) { + case nct6775: + data->have_vid = (cr2a & 0x40); + break; + case nct6776: + data->have_vid = (cr2a & 0x60) == 0x40; + break; + case nct6106: + case nct6116: + case nct6779: + case nct6791: + case nct6792: + case nct6793: + case nct6795: + case nct6796: + case nct6797: + case nct6798: + break; + } + + /* + * Read VID value + * We can get the VID input values directly at logical device D 0xe3. + */ + if (data->have_vid) { + sio_data->sio_select(sio_data, NCT6775_LD_VID); + data->vid = sio_data->sio_inb(sio_data, 0xe3); + data->vrm = vid_which_vrm(); + } + + if (fan_debounce) { + u8 tmp; + + sio_data->sio_select(sio_data, NCT6775_LD_HWM); + tmp = sio_data->sio_inb(sio_data, + NCT6775_REG_CR_FAN_DEBOUNCE); + switch (data->kind) { + case nct6106: + case nct6116: + tmp |= 0xe0; + break; + case nct6775: + tmp |= 0x1e; + break; + case nct6776: + case nct6779: + tmp |= 0x3e; + break; + case nct6791: + case nct6792: + case nct6793: + case nct6795: + case nct6796: + case nct6797: + case nct6798: + tmp |= 0x7e; + break; + } + sio_data->sio_outb(sio_data, NCT6775_REG_CR_FAN_DEBOUNCE, + tmp); + pr_info("Enabled fan debounce for chip %s\n", data->name); + } + + nct6775_check_fan_inputs(data, sio_data); + + sio_data->sio_exit(sio_data); + + return nct6775_add_attr_group(data, &nct6775_group_other); +} + +static const struct regmap_config nct6775_regmap_config = { + .reg_bits = 16, + .val_bits = 16, + .reg_read = nct6775_reg_read, + .reg_write = nct6775_reg_write, +}; + +static const struct regmap_config nct6775_wmi_regmap_config = { + .reg_bits = 16, + .val_bits = 16, + .reg_read = nct6775_wmi_reg_read, + .reg_write = nct6775_wmi_reg_write, +}; + +static int nct6775_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nct6775_sio_data *sio_data = dev_get_platdata(dev); + struct nct6775_data *data; + struct resource *res; + const struct regmap_config *regmapcfg; + + if (sio_data->access == access_direct) { + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(&pdev->dev, res->start, IOREGION_LENGTH, DRVNAME)) + return -EBUSY; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->kind = sio_data->kind; + data->sioreg = sio_data->sioreg; + + if (sio_data->access == access_direct) { + data->addr = res->start; + regmapcfg = &nct6775_regmap_config; + } else { + regmapcfg = &nct6775_wmi_regmap_config; + } + + platform_set_drvdata(pdev, data); + + data->driver_data = sio_data; + data->driver_init = nct6775_platform_probe_init; + + return nct6775_probe(&pdev->dev, data, regmapcfg); +} + +static struct platform_driver nct6775_driver = { + .driver = { + .name = DRVNAME, + .pm = &nct6775_dev_pm_ops, + }, + .probe = nct6775_platform_probe, +}; + +/* nct6775_find() looks for a '627 in the Super-I/O config space */ +static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data) +{ + u16 val; + int err; + int addr; + + sio_data->access = access_direct; + sio_data->sioreg = sioaddr; + + err = sio_data->sio_enter(sio_data); + if (err) + return err; + + val = (sio_data->sio_inb(sio_data, SIO_REG_DEVID) << 8) | + sio_data->sio_inb(sio_data, SIO_REG_DEVID + 1); + if (force_id && val != 0xffff) + val = force_id; + + switch (val & SIO_ID_MASK) { + case SIO_NCT6106_ID: + sio_data->kind = nct6106; + break; + case SIO_NCT6116_ID: + sio_data->kind = nct6116; + break; + case SIO_NCT6775_ID: + sio_data->kind = nct6775; + break; + case SIO_NCT6776_ID: + sio_data->kind = nct6776; + break; + case SIO_NCT6779_ID: + sio_data->kind = nct6779; + break; + case SIO_NCT6791_ID: + sio_data->kind = nct6791; + break; + case SIO_NCT6792_ID: + sio_data->kind = nct6792; + break; + case SIO_NCT6793_ID: + sio_data->kind = nct6793; + break; + case SIO_NCT6795_ID: + sio_data->kind = nct6795; + break; + case SIO_NCT6796_ID: + sio_data->kind = nct6796; + break; + case SIO_NCT6797_ID: + sio_data->kind = nct6797; + break; + case SIO_NCT6798_ID: + sio_data->kind = nct6798; + break; + default: + if (val != 0xffff) + pr_debug("unsupported chip ID: 0x%04x\n", val); + sio_data->sio_exit(sio_data); + return -ENODEV; + } + + /* We have a known chip, find the HWM I/O address */ + sio_data->sio_select(sio_data, NCT6775_LD_HWM); + val = (sio_data->sio_inb(sio_data, SIO_REG_ADDR) << 8) + | sio_data->sio_inb(sio_data, SIO_REG_ADDR + 1); + addr = val & IOREGION_ALIGNMENT; + if (addr == 0) { + pr_err("Refusing to enable a Super-I/O device with a base I/O port 0\n"); + sio_data->sio_exit(sio_data); + return -ENODEV; + } + + /* Activate logical device if needed */ + val = sio_data->sio_inb(sio_data, SIO_REG_ENABLE); + if (!(val & 0x01)) { + pr_warn("Forcibly enabling Super-I/O. Sensor is probably unusable.\n"); + sio_data->sio_outb(sio_data, SIO_REG_ENABLE, val | 0x01); + } + + if (sio_data->kind == nct6791 || sio_data->kind == nct6792 || + sio_data->kind == nct6793 || sio_data->kind == nct6795 || + sio_data->kind == nct6796 || sio_data->kind == nct6797 || + sio_data->kind == nct6798) + nct6791_enable_io_mapping(sio_data); + + sio_data->sio_exit(sio_data); + pr_info("Found %s or compatible chip at %#x:%#x\n", + nct6775_sio_names[sio_data->kind], sioaddr, addr); + + return addr; +} + +/* + * when Super-I/O functions move to a separate file, the Super-I/O + * bus will manage the lifetime of the device and this module will only keep + * track of the nct6775 driver. But since we use platform_device_alloc(), we + * must keep track of the device + */ +static struct platform_device *pdev[2]; + +static const char * const asus_wmi_boards[] = { + "PRO H410T", + "ProArt X570-CREATOR WIFI", + "Pro B550M-C", + "Pro WS X570-ACE", + "PRIME B360-PLUS", + "PRIME B460-PLUS", + "PRIME B550-PLUS", + "PRIME B550M-A", + "PRIME B550M-A (WI-FI)", + "PRIME H410M-R", + "PRIME X570-P", + "PRIME X570-PRO", + "ROG CROSSHAIR VIII DARK HERO", + "ROG CROSSHAIR VIII FORMULA", + "ROG CROSSHAIR VIII HERO", + "ROG CROSSHAIR VIII IMPACT", + "ROG STRIX B550-A GAMING", + "ROG STRIX B550-E GAMING", + "ROG STRIX B550-F GAMING", + "ROG STRIX B550-F GAMING (WI-FI)", + "ROG STRIX B550-F GAMING WIFI II", + "ROG STRIX B550-I GAMING", + "ROG STRIX B550-XE GAMING (WI-FI)", + "ROG STRIX X570-E GAMING", + "ROG STRIX X570-E GAMING WIFI II", + "ROG STRIX X570-F GAMING", + "ROG STRIX X570-I GAMING", + "ROG STRIX Z390-E GAMING", + "ROG STRIX Z390-F GAMING", + "ROG STRIX Z390-H GAMING", + "ROG STRIX Z390-I GAMING", + "ROG STRIX Z490-A GAMING", + "ROG STRIX Z490-E GAMING", + "ROG STRIX Z490-F GAMING", + "ROG STRIX Z490-G GAMING", + "ROG STRIX Z490-G GAMING (WI-FI)", + "ROG STRIX Z490-H GAMING", + "ROG STRIX Z490-I GAMING", + "TUF GAMING B550M-PLUS", + "TUF GAMING B550M-PLUS (WI-FI)", + "TUF GAMING B550-PLUS", + "TUF GAMING B550-PRO", + "TUF GAMING X570-PLUS", + "TUF GAMING X570-PLUS (WI-FI)", + "TUF GAMING X570-PRO (WI-FI)", + "TUF GAMING Z490-PLUS", + "TUF GAMING Z490-PLUS (WI-FI)", +}; + +static int __init sensors_nct6775_platform_init(void) +{ + int i, err; + bool found = false; + int address; + struct resource res; + struct nct6775_sio_data sio_data; + int sioaddr[2] = { 0x2e, 0x4e }; + enum sensor_access access = access_direct; + const char *board_vendor, *board_name; + u8 tmp; + + err = platform_driver_register(&nct6775_driver); + if (err) + return err; + + board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + board_name = dmi_get_system_info(DMI_BOARD_NAME); + + if (board_name && board_vendor && + !strcmp(board_vendor, "ASUSTeK COMPUTER INC.")) { + err = match_string(asus_wmi_boards, ARRAY_SIZE(asus_wmi_boards), + board_name); + if (err >= 0) { + /* if reading chip id via WMI succeeds, use WMI */ + if (!nct6775_asuswmi_read(0, NCT6775_PORT_CHIPID, &tmp) && tmp) { + pr_info("Using Asus WMI to access %#x chip.\n", tmp); + access = access_asuswmi; + } else { + pr_err("Can't read ChipID by Asus WMI.\n"); + } + } + } + + /* + * initialize sio_data->kind and sio_data->sioreg. + * + * when Super-I/O functions move to a separate file, the Super-I/O + * driver will probe 0x2e and 0x4e and auto-detect the presence of a + * nct6775 hardware monitor, and call probe() + */ + for (i = 0; i < ARRAY_SIZE(pdev); i++) { + sio_data.sio_outb = superio_outb; + sio_data.sio_inb = superio_inb; + sio_data.sio_select = superio_select; + sio_data.sio_enter = superio_enter; + sio_data.sio_exit = superio_exit; + + address = nct6775_find(sioaddr[i], &sio_data); + if (address <= 0) + continue; + + found = true; + + sio_data.access = access; + + if (access == access_asuswmi) { + sio_data.sio_outb = superio_wmi_outb; + sio_data.sio_inb = superio_wmi_inb; + sio_data.sio_select = superio_wmi_select; + sio_data.sio_enter = superio_wmi_enter; + sio_data.sio_exit = superio_wmi_exit; + } + + pdev[i] = platform_device_alloc(DRVNAME, address); + if (!pdev[i]) { + err = -ENOMEM; + goto exit_device_unregister; + } + + err = platform_device_add_data(pdev[i], &sio_data, + sizeof(struct nct6775_sio_data)); + if (err) + goto exit_device_put; + + if (sio_data.access == access_direct) { + memset(&res, 0, sizeof(res)); + res.name = DRVNAME; + res.start = address + IOREGION_OFFSET; + res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1; + res.flags = IORESOURCE_IO; + + err = acpi_check_resource_conflict(&res); + if (err) { + platform_device_put(pdev[i]); + pdev[i] = NULL; + continue; + } + + err = platform_device_add_resources(pdev[i], &res, 1); + if (err) + goto exit_device_put; + } + + /* platform_device_add calls probe() */ + err = platform_device_add(pdev[i]); + if (err) + goto exit_device_put; + } + if (!found) { + err = -ENODEV; + goto exit_unregister; + } + + return 0; + +exit_device_put: + platform_device_put(pdev[i]); +exit_device_unregister: + while (--i >= 0) { + if (pdev[i]) + platform_device_unregister(pdev[i]); + } +exit_unregister: + platform_driver_unregister(&nct6775_driver); + return err; +} + +static void __exit sensors_nct6775_platform_exit(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pdev); i++) { + if (pdev[i]) + platform_device_unregister(pdev[i]); + } + platform_driver_unregister(&nct6775_driver); +} + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("Platform driver for NCT6775F and compatible chips"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(HWMON_NCT6775); + +module_init(sensors_nct6775_platform_init); +module_exit(sensors_nct6775_platform_exit); diff --git a/drivers/hwmon/nct6775.h b/drivers/hwmon/nct6775.h new file mode 100644 index 000000000000..93f708148e65 --- /dev/null +++ b/drivers/hwmon/nct6775.h @@ -0,0 +1,252 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __HWMON_NCT6775_H__ +#define __HWMON_NCT6775_H__ + +#include + +enum kinds { nct6106, nct6116, nct6775, nct6776, nct6779, nct6791, nct6792, + nct6793, nct6795, nct6796, nct6797, nct6798 }; +enum pwm_enable { off, manual, thermal_cruise, speed_cruise, sf3, sf4 }; + +#define NUM_TEMP 10 /* Max number of temp attribute sets w/ limits*/ +#define NUM_TEMP_FIXED 6 /* Max number of fixed temp attribute sets */ +#define NUM_TSI_TEMP 8 /* Max number of TSI temp register pairs */ + +#define NUM_REG_ALARM 7 /* Max number of alarm registers */ +#define NUM_REG_BEEP 5 /* Max number of beep registers */ + +#define NUM_FAN 7 + +struct nct6775_data { + int addr; /* IO base of hw monitor block */ + int sioreg; /* SIO register address */ + enum kinds kind; + const char *name; + + const struct attribute_group *groups[7]; + u8 num_groups; + + u16 reg_temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst, + * 3=temp_crit, 4=temp_lcrit + */ + u8 temp_src[NUM_TEMP]; + u16 reg_temp_config[NUM_TEMP]; + const char * const *temp_label; + u32 temp_mask; + u32 virt_temp_mask; + + u16 REG_CONFIG; + u16 REG_VBAT; + u16 REG_DIODE; + u8 DIODE_MASK; + + const s8 *ALARM_BITS; + const s8 *BEEP_BITS; + + const u16 *REG_VIN; + const u16 *REG_IN_MINMAX[2]; + + const u16 *REG_TARGET; + const u16 *REG_FAN; + const u16 *REG_FAN_MODE; + const u16 *REG_FAN_MIN; + const u16 *REG_FAN_PULSES; + const u16 *FAN_PULSE_SHIFT; + const u16 *REG_FAN_TIME[3]; + + const u16 *REG_TOLERANCE_H; + + const u8 *REG_PWM_MODE; + const u8 *PWM_MODE_MASK; + + const u16 *REG_PWM[7]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor, + * [3]=pwm_max, [4]=pwm_step, + * [5]=weight_duty_step, [6]=weight_duty_base + */ + const u16 *REG_PWM_READ; + + const u16 *REG_CRITICAL_PWM_ENABLE; + u8 CRITICAL_PWM_ENABLE_MASK; + const u16 *REG_CRITICAL_PWM; + + const u16 *REG_AUTO_TEMP; + const u16 *REG_AUTO_PWM; + + const u16 *REG_CRITICAL_TEMP; + const u16 *REG_CRITICAL_TEMP_TOLERANCE; + + const u16 *REG_TEMP_SOURCE; /* temp register sources */ + const u16 *REG_TEMP_SEL; + const u16 *REG_WEIGHT_TEMP_SEL; + const u16 *REG_WEIGHT_TEMP[3]; /* 0=base, 1=tolerance, 2=step */ + + const u16 *REG_TEMP_OFFSET; + + const u16 *REG_ALARM; + const u16 *REG_BEEP; + + const u16 *REG_TSI_TEMP; + + unsigned int (*fan_from_reg)(u16 reg, unsigned int divreg); + unsigned int (*fan_from_reg_min)(u16 reg, unsigned int divreg); + + struct mutex update_lock; + bool valid; /* true if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* Register values */ + u8 bank; /* current register bank */ + u8 in_num; /* number of in inputs we have */ + u8 in[15][3]; /* [0]=in, [1]=in_max, [2]=in_min */ + unsigned int rpm[NUM_FAN]; + u16 fan_min[NUM_FAN]; + u8 fan_pulses[NUM_FAN]; + u8 fan_div[NUM_FAN]; + u8 has_pwm; + u8 has_fan; /* some fan inputs can be disabled */ + u8 has_fan_min; /* some fans don't have min register */ + bool has_fan_div; + + u8 num_temp_alarms; /* 2, 3, or 6 */ + u8 num_temp_beeps; /* 2, 3, or 6 */ + u8 temp_fixed_num; /* 3 or 6 */ + u8 temp_type[NUM_TEMP_FIXED]; + s8 temp_offset[NUM_TEMP_FIXED]; + s16 temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst, + * 3=temp_crit, 4=temp_lcrit + */ + s16 tsi_temp[NUM_TSI_TEMP]; + u64 alarms; + u64 beeps; + + u8 pwm_num; /* number of pwm */ + u8 pwm_mode[NUM_FAN]; /* 0->DC variable voltage, + * 1->PWM variable duty cycle + */ + enum pwm_enable pwm_enable[NUM_FAN]; + /* 0->off + * 1->manual + * 2->thermal cruise mode (also called SmartFan I) + * 3->fan speed cruise mode + * 4->SmartFan III + * 5->enhanced variable thermal cruise (SmartFan IV) + */ + u8 pwm[7][NUM_FAN]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor, + * [3]=pwm_max, [4]=pwm_step, + * [5]=weight_duty_step, [6]=weight_duty_base + */ + + u8 target_temp[NUM_FAN]; + u8 target_temp_mask; + u32 target_speed[NUM_FAN]; + u32 target_speed_tolerance[NUM_FAN]; + u8 speed_tolerance_limit; + + u8 temp_tolerance[2][NUM_FAN]; + u8 tolerance_mask; + + u8 fan_time[3][NUM_FAN]; /* 0 = stop_time, 1 = step_up, 2 = step_down */ + + /* Automatic fan speed control registers */ + int auto_pwm_num; + u8 auto_pwm[NUM_FAN][7]; + u8 auto_temp[NUM_FAN][7]; + u8 pwm_temp_sel[NUM_FAN]; + u8 pwm_weight_temp_sel[NUM_FAN]; + u8 weight_temp[3][NUM_FAN]; /* 0->temp_step, 1->temp_step_tol, + * 2->temp_base + */ + + u8 vid; + u8 vrm; + + bool have_vid; + + u16 have_temp; + u16 have_temp_fixed; + u16 have_tsi_temp; + u16 have_in; + + /* Remember extra register values over suspend/resume */ + u8 vbat; + u8 fandiv1; + u8 fandiv2; + u8 sio_reg_enable; + + struct regmap *regmap; + bool read_only; + + /* driver-specific (platform, i2c) initialization hook and data */ + int (*driver_init)(struct nct6775_data *data); + void *driver_data; +}; + +static inline int nct6775_read_value(struct nct6775_data *data, u16 reg, u16 *value) +{ + unsigned int tmp; + int ret = regmap_read(data->regmap, reg, &tmp); + + if (!ret) + *value = tmp; + return ret; +} + +static inline int nct6775_write_value(struct nct6775_data *data, u16 reg, u16 value) +{ + return regmap_write(data->regmap, reg, value); +} + +bool nct6775_reg_is_word_sized(struct nct6775_data *data, u16 reg); +int nct6775_probe(struct device *dev, struct nct6775_data *data, + const struct regmap_config *regmapcfg); + +ssize_t nct6775_show_alarm(struct device *dev, struct device_attribute *attr, char *buf); +ssize_t nct6775_show_beep(struct device *dev, struct device_attribute *attr, char *buf); +ssize_t nct6775_store_beep(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count); + +static inline int nct6775_write_temp(struct nct6775_data *data, u16 reg, u16 value) +{ + if (!nct6775_reg_is_word_sized(data, reg)) + value >>= 8; + return nct6775_write_value(data, reg, value); +} + +static inline umode_t nct6775_attr_mode(struct nct6775_data *data, struct attribute *attr) +{ + return data->read_only ? (attr->mode & ~0222) : attr->mode; +} + +static inline int +nct6775_add_attr_group(struct nct6775_data *data, const struct attribute_group *group) +{ + /* Need to leave a NULL terminator at the end of data->groups */ + if (data->num_groups == ARRAY_SIZE(data->groups) - 1) + return -ENOBUFS; + + data->groups[data->num_groups++] = group; + return 0; +} + +#define NCT6775_REG_BANK 0x4E +#define NCT6775_REG_CONFIG 0x40 + +#define NCT6775_REG_FANDIV1 0x506 +#define NCT6775_REG_FANDIV2 0x507 + +#define NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE 0x28 + +#define FAN_ALARM_BASE 16 +#define TEMP_ALARM_BASE 24 +#define INTRUSION_ALARM_BASE 30 +#define BEEP_ENABLE_BASE 15 + +/* + * Not currently used: + * REG_MAN_ID has the value 0x5ca3 for all supported chips. + * REG_CHIP_ID == 0x88/0xa1/0xc1 depending on chip model. + * REG_MAN_ID is at port 0x4f + * REG_CHIP_ID is at port 0x58 + */ + +#endif /* __HWMON_NCT6775_H__ */ diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index f00cd59f1d19..d78f4bebc718 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -1149,44 +1149,75 @@ static void occ_parse_poll_response(struct occ *occ) sizeof(*header), size + sizeof(*header)); } -int occ_setup(struct occ *occ, const char *name) +int occ_active(struct occ *occ, bool active) +{ + int rc = mutex_lock_interruptible(&occ->lock); + + if (rc) + return rc; + + if (active) { + if (occ->active) { + rc = -EALREADY; + goto unlock; + } + + occ->error_count = 0; + occ->last_safe = 0; + + rc = occ_poll(occ); + if (rc < 0) { + dev_err(occ->bus_dev, + "failed to get OCC poll response=%02x: %d\n", + occ->resp.return_status, rc); + goto unlock; + } + + occ->active = true; + occ->next_update = jiffies + OCC_UPDATE_FREQUENCY; + occ_parse_poll_response(occ); + + rc = occ_setup_sensor_attrs(occ); + if (rc) { + dev_err(occ->bus_dev, + "failed to setup sensor attrs: %d\n", rc); + goto unlock; + } + + occ->hwmon = hwmon_device_register_with_groups(occ->bus_dev, + "occ", occ, + occ->groups); + if (IS_ERR(occ->hwmon)) { + rc = PTR_ERR(occ->hwmon); + occ->hwmon = NULL; + dev_err(occ->bus_dev, + "failed to register hwmon device: %d\n", rc); + goto unlock; + } + } else { + if (!occ->active) { + rc = -EALREADY; + goto unlock; + } + + if (occ->hwmon) + hwmon_device_unregister(occ->hwmon); + occ->active = false; + occ->hwmon = NULL; + } + +unlock: + mutex_unlock(&occ->lock); + return rc; +} + +int occ_setup(struct occ *occ) { int rc; mutex_init(&occ->lock); occ->groups[0] = &occ->group; - /* no need to lock */ - rc = occ_poll(occ); - if (rc == -ESHUTDOWN) { - dev_info(occ->bus_dev, "host is not ready\n"); - return rc; - } else if (rc < 0) { - dev_err(occ->bus_dev, - "failed to get OCC poll response=%02x: %d\n", - occ->resp.return_status, rc); - return rc; - } - - occ->next_update = jiffies + OCC_UPDATE_FREQUENCY; - occ_parse_poll_response(occ); - - rc = occ_setup_sensor_attrs(occ); - if (rc) { - dev_err(occ->bus_dev, "failed to setup sensor attrs: %d\n", - rc); - return rc; - } - - occ->hwmon = devm_hwmon_device_register_with_groups(occ->bus_dev, name, - occ, occ->groups); - if (IS_ERR(occ->hwmon)) { - rc = PTR_ERR(occ->hwmon); - dev_err(occ->bus_dev, "failed to register hwmon device: %d\n", - rc); - return rc; - } - rc = occ_setup_sysfs(occ); if (rc) dev_err(occ->bus_dev, "failed to setup sysfs: %d\n", rc); @@ -1195,6 +1226,15 @@ int occ_setup(struct occ *occ, const char *name) } EXPORT_SYMBOL_GPL(occ_setup); +void occ_shutdown(struct occ *occ) +{ + occ_shutdown_sysfs(occ); + + if (occ->hwmon) + hwmon_device_unregister(occ->hwmon); +} +EXPORT_SYMBOL_GPL(occ_shutdown); + MODULE_AUTHOR("Eddie James "); MODULE_DESCRIPTION("Common OCC hwmon code"); MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h index 2dd4a4d240c0..64d5ec7e169b 100644 --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -106,6 +106,7 @@ struct occ { struct attribute_group group; const struct attribute_group *groups[2]; + bool active; int error; /* final transfer error after retry */ int last_error; /* latest transfer error */ unsigned int error_count; /* number of xfr errors observed */ @@ -123,9 +124,11 @@ struct occ { u8 prev_mode; }; -int occ_setup(struct occ *occ, const char *name); +int occ_active(struct occ *occ, bool active); +int occ_setup(struct occ *occ); int occ_setup_sysfs(struct occ *occ); void occ_shutdown(struct occ *occ); +void occ_shutdown_sysfs(struct occ *occ); void occ_sysfs_poll_done(struct occ *occ); int occ_update_response(struct occ *occ); diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c index 9e61e1fb5142..da39ea28df31 100644 --- a/drivers/hwmon/occ/p8_i2c.c +++ b/drivers/hwmon/occ/p8_i2c.c @@ -223,7 +223,7 @@ static int p8_i2c_occ_probe(struct i2c_client *client) occ->poll_cmd_data = 0x10; /* P8 OCC poll data */ occ->send_cmd = p8_i2c_occ_send_cmd; - return occ_setup(occ, "p8_occ"); + return occ_setup(occ); } static int p8_i2c_occ_remove(struct i2c_client *client) diff --git a/drivers/hwmon/occ/p9_sbe.c b/drivers/hwmon/occ/p9_sbe.c index 49b13cc01073..42fc7b97bb34 100644 --- a/drivers/hwmon/occ/p9_sbe.c +++ b/drivers/hwmon/occ/p9_sbe.c @@ -145,7 +145,7 @@ static int p9_sbe_occ_probe(struct platform_device *pdev) occ->poll_cmd_data = 0x20; /* P9 OCC poll data */ occ->send_cmd = p9_sbe_occ_send_cmd; - rc = occ_setup(occ, "p9_occ"); + rc = occ_setup(occ); if (rc == -ESHUTDOWN) rc = -ENODEV; /* Host is shutdown, don't spew errors */ diff --git a/drivers/hwmon/occ/sysfs.c b/drivers/hwmon/occ/sysfs.c index b2f788a77746..2317301fc1e9 100644 --- a/drivers/hwmon/occ/sysfs.c +++ b/drivers/hwmon/occ/sysfs.c @@ -6,13 +6,13 @@ #include #include #include +#include #include #include "common.h" /* OCC status register */ #define OCC_STAT_MASTER BIT(7) -#define OCC_STAT_ACTIVE BIT(0) /* OCC extended status register */ #define OCC_EXT_STAT_DVFS_OT BIT(7) @@ -22,6 +22,25 @@ #define OCC_EXT_STAT_DVFS_VDD BIT(3) #define OCC_EXT_STAT_GPU_THROTTLE GENMASK(2, 0) +static ssize_t occ_active_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int rc; + bool active; + struct occ *occ = dev_get_drvdata(dev); + + rc = kstrtobool(buf, &active); + if (rc) + return rc; + + rc = occ_active(occ, active); + if (rc) + return rc; + + return count; +} + static ssize_t occ_sysfs_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -31,54 +50,64 @@ static ssize_t occ_sysfs_show(struct device *dev, struct occ_poll_response_header *header; struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); - rc = occ_update_response(occ); - if (rc) - return rc; + if (occ->active) { + rc = occ_update_response(occ); + if (rc) + return rc; - header = (struct occ_poll_response_header *)occ->resp.data; + header = (struct occ_poll_response_header *)occ->resp.data; - switch (sattr->index) { - case 0: - val = !!(header->status & OCC_STAT_MASTER); - break; - case 1: - val = !!(header->status & OCC_STAT_ACTIVE); - break; - case 2: - val = !!(header->ext_status & OCC_EXT_STAT_DVFS_OT); - break; - case 3: - val = !!(header->ext_status & OCC_EXT_STAT_DVFS_POWER); - break; - case 4: - val = !!(header->ext_status & OCC_EXT_STAT_MEM_THROTTLE); - break; - case 5: - val = !!(header->ext_status & OCC_EXT_STAT_QUICK_DROP); - break; - case 6: - val = header->occ_state; - break; - case 7: - if (header->status & OCC_STAT_MASTER) - val = hweight8(header->occs_present); - else + switch (sattr->index) { + case 0: + val = !!(header->status & OCC_STAT_MASTER); + break; + case 1: val = 1; - break; - case 8: - val = header->ips_status; - break; - case 9: - val = header->mode; - break; - case 10: - val = !!(header->ext_status & OCC_EXT_STAT_DVFS_VDD); - break; - case 11: - val = header->ext_status & OCC_EXT_STAT_GPU_THROTTLE; - break; - default: - return -EINVAL; + break; + case 2: + val = !!(header->ext_status & OCC_EXT_STAT_DVFS_OT); + break; + case 3: + val = !!(header->ext_status & OCC_EXT_STAT_DVFS_POWER); + break; + case 4: + val = !!(header->ext_status & + OCC_EXT_STAT_MEM_THROTTLE); + break; + case 5: + val = !!(header->ext_status & OCC_EXT_STAT_QUICK_DROP); + break; + case 6: + val = header->occ_state; + break; + case 7: + if (header->status & OCC_STAT_MASTER) + val = hweight8(header->occs_present); + else + val = 1; + break; + case 8: + val = header->ips_status; + break; + case 9: + val = header->mode; + break; + case 10: + val = !!(header->ext_status & OCC_EXT_STAT_DVFS_VDD); + break; + case 11: + val = header->ext_status & OCC_EXT_STAT_GPU_THROTTLE; + break; + default: + return -EINVAL; + } + } else { + if (sattr->index == 1) + val = 0; + else if (sattr->index <= 11) + val = -ENODATA; + else + return -EINVAL; } return sysfs_emit(buf, "%d\n", val); @@ -95,7 +124,8 @@ static ssize_t occ_error_show(struct device *dev, } static SENSOR_DEVICE_ATTR(occ_master, 0444, occ_sysfs_show, NULL, 0); -static SENSOR_DEVICE_ATTR(occ_active, 0444, occ_sysfs_show, NULL, 1); +static SENSOR_DEVICE_ATTR(occ_active, 0644, occ_sysfs_show, occ_active_store, + 1); static SENSOR_DEVICE_ATTR(occ_dvfs_overtemp, 0444, occ_sysfs_show, NULL, 2); static SENSOR_DEVICE_ATTR(occ_dvfs_power, 0444, occ_sysfs_show, NULL, 3); static SENSOR_DEVICE_ATTR(occ_mem_throttle, 0444, occ_sysfs_show, NULL, 4); @@ -139,7 +169,7 @@ void occ_sysfs_poll_done(struct occ *occ) * On the first poll response, we haven't yet created the sysfs * attributes, so don't make any notify calls. */ - if (!occ->hwmon) + if (!occ->active) goto done; if ((header->status & OCC_STAT_MASTER) != @@ -148,12 +178,6 @@ void occ_sysfs_poll_done(struct occ *occ) sysfs_notify(&occ->bus_dev->kobj, NULL, name); } - if ((header->status & OCC_STAT_ACTIVE) != - (occ->prev_stat & OCC_STAT_ACTIVE)) { - name = sensor_dev_attr_occ_active.dev_attr.attr.name; - sysfs_notify(&occ->bus_dev->kobj, NULL, name); - } - if ((header->ext_status & OCC_EXT_STAT_DVFS_OT) != (occ->prev_ext_stat & OCC_EXT_STAT_DVFS_OT)) { name = sensor_dev_attr_occ_dvfs_overtemp.dev_attr.attr.name; @@ -227,8 +251,7 @@ int occ_setup_sysfs(struct occ *occ) return sysfs_create_group(&occ->bus_dev->kobj, &occ_sysfs); } -void occ_shutdown(struct occ *occ) +void occ_shutdown_sysfs(struct occ *occ) { sysfs_remove_group(&occ->bus_dev->kobj, &occ_sysfs); } -EXPORT_SYMBOL_GPL(occ_shutdown); diff --git a/drivers/hwmon/peci/cputemp.c b/drivers/hwmon/peci/cputemp.c index 12156328f5cf..57470fda5f6c 100644 --- a/drivers/hwmon/peci/cputemp.c +++ b/drivers/hwmon/peci/cputemp.c @@ -447,29 +447,23 @@ static const struct hwmon_ops peci_cputemp_ops = { .read = cputemp_read, }; -static const u32 peci_cputemp_temp_channel_config[] = { - /* Die temperature */ - HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_CRIT_HYST, - /* DTS margin */ - HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_CRIT_HYST, - /* Tcontrol temperature */ - HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_CRIT, - /* Tthrottle temperature */ - HWMON_T_LABEL | HWMON_T_INPUT, - /* Tjmax temperature */ - HWMON_T_LABEL | HWMON_T_INPUT, - /* Core temperature - for all core channels */ - [channel_core ... CPUTEMP_CHANNEL_NUMS - 1] = HWMON_T_LABEL | HWMON_T_INPUT, - 0 -}; - -static const struct hwmon_channel_info peci_cputemp_temp_channel = { - .type = hwmon_temp, - .config = peci_cputemp_temp_channel_config, -}; - static const struct hwmon_channel_info *peci_cputemp_info[] = { - &peci_cputemp_temp_channel, + HWMON_CHANNEL_INFO(temp, + /* Die temperature */ + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | + HWMON_T_CRIT | HWMON_T_CRIT_HYST, + /* DTS margin */ + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | + HWMON_T_CRIT | HWMON_T_CRIT_HYST, + /* Tcontrol temperature */ + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_CRIT, + /* Tthrottle temperature */ + HWMON_T_LABEL | HWMON_T_INPUT, + /* Tjmax temperature */ + HWMON_T_LABEL | HWMON_T_INPUT, + /* Core temperature - for all core channels */ + [channel_core ... CPUTEMP_CHANNEL_NUMS - 1] = + HWMON_T_LABEL | HWMON_T_INPUT), NULL }; diff --git a/drivers/hwmon/peci/dimmtemp.c b/drivers/hwmon/peci/dimmtemp.c index c8222354c005..0a633bda3668 100644 --- a/drivers/hwmon/peci/dimmtemp.c +++ b/drivers/hwmon/peci/dimmtemp.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -219,7 +220,7 @@ static int check_populated_dimms(struct peci_dimmtemp *priv) int chan_rank_max = priv->gen_info->chan_rank_max; int dimm_idx_max = priv->gen_info->dimm_idx_max; u32 chan_rank_empty = 0; - u64 dimm_mask = 0; + u32 dimm_mask = 0; int chan_rank, dimm_idx, ret; u32 pcs; @@ -278,9 +279,9 @@ static int check_populated_dimms(struct peci_dimmtemp *priv) return -EAGAIN; } - dev_dbg(priv->dev, "Scanned populated DIMMs: %#llx\n", dimm_mask); + dev_dbg(priv->dev, "Scanned populated DIMMs: %#x\n", dimm_mask); - bitmap_from_u64(priv->dimm_mask, dimm_mask); + bitmap_from_arr32(priv->dimm_mask, &dimm_mask, DIMM_NUMS_MAX); return 0; } @@ -299,18 +300,10 @@ static int create_dimm_temp_label(struct peci_dimmtemp *priv, int chan) return 0; } -static const u32 peci_dimmtemp_temp_channel_config[] = { - [0 ... DIMM_NUMS_MAX - 1] = HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT, - 0 -}; - -static const struct hwmon_channel_info peci_dimmtemp_temp_channel = { - .type = hwmon_temp, - .config = peci_dimmtemp_temp_channel_config, -}; - static const struct hwmon_channel_info *peci_dimmtemp_temp_info[] = { - &peci_dimmtemp_temp_channel, + HWMON_CHANNEL_INFO(temp, + [0 ... DIMM_NUMS_MAX - 1] = HWMON_T_LABEL | + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT), NULL }; @@ -378,13 +371,6 @@ static void create_dimm_temp_info_delayed(struct work_struct *work) dev_err(priv->dev, "Failed to populate DIMM temp info\n"); } -static void remove_delayed_work(void *_priv) -{ - struct peci_dimmtemp *priv = _priv; - - cancel_delayed_work_sync(&priv->detect_work); -} - static int peci_dimmtemp_probe(struct auxiliary_device *adev, const struct auxiliary_device_id *id) { struct device *dev = &adev->dev; @@ -415,9 +401,8 @@ static int peci_dimmtemp_probe(struct auxiliary_device *adev, const struct auxil "Unexpected PECI revision %#x, some features may be unavailable\n", peci_dev->info.peci_revision); - INIT_DELAYED_WORK(&priv->detect_work, create_dimm_temp_info_delayed); - - ret = devm_add_action_or_reset(priv->dev, remove_delayed_work, priv); + ret = devm_delayed_work_autocancel(priv->dev, &priv->detect_work, + create_dimm_temp_info_delayed); if (ret) return ret; diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index a2ea1d5a8765..dfae76db65ae 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -228,10 +228,10 @@ config SENSORS_MAX16064 be called max16064. config SENSORS_MAX16601 - tristate "Maxim MAX16508, MAX16601" + tristate "Maxim MAX16508, MAX16601, MAX16602" help If you say yes here you get hardware monitoring support for Maxim - MAX16508 and MAX16601. + MAX16508, MAX16601 and MAX16602. This driver can also be built as a module. If so, the module will be called max16601. @@ -408,6 +408,15 @@ config SENSORS_UCD9200 This driver can also be built as a module. If so, the module will be called ucd9200. +config SENSORS_XDPE152 + tristate "Infineon XDPE152 family" + help + If you say yes here you get hardware monitoring support for Infineon + XDPE15284, XDPE152C4, device. + + This driver can also be built as a module. If so, the module will + be called xdpe152c4. + config SENSORS_XDPE122 tristate "Infineon XDPE122 family" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index a4a96ac71de7..4678fba5012c 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -43,5 +43,6 @@ obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o +obj-$(CONFIG_SENSORS_XDPE152) += xdpe152c4.o obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o obj-$(CONFIG_SENSORS_PIM4328) += pim4328.o diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c index 0127273883f0..531aa674a928 100644 --- a/drivers/hwmon/pmbus/ltc2978.c +++ b/drivers/hwmon/pmbus/ltc2978.c @@ -196,6 +196,17 @@ static int ltc_read_byte_data(struct i2c_client *client, int page, int reg) return pmbus_read_byte_data(client, page, reg); } +static int ltc_write_byte_data(struct i2c_client *client, int page, int reg, u8 value) +{ + int ret; + + ret = ltc_wait_ready(client); + if (ret < 0) + return ret; + + return pmbus_write_byte_data(client, page, reg, value); +} + static int ltc_write_byte(struct i2c_client *client, int page, u8 byte) { int ret; @@ -681,6 +692,7 @@ static int ltc2978_probe(struct i2c_client *client) info = &data->info; info->write_word_data = ltc2978_write_word_data; info->write_byte = ltc_write_byte; + info->write_byte_data = ltc_write_byte_data; info->read_word_data = ltc_read_word_data; info->read_byte_data = ltc_read_byte_data; diff --git a/drivers/hwmon/pmbus/max16601.c b/drivers/hwmon/pmbus/max16601.c index 5a226a564776..b628405e6586 100644 --- a/drivers/hwmon/pmbus/max16601.c +++ b/drivers/hwmon/pmbus/max16601.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Hardware monitoring driver for Maxim MAX16508 and MAX16601. + * Hardware monitoring driver for Maxim MAX16508, MAX16601 and MAX16602. * * Implementation notes: * @@ -31,7 +31,7 @@ #include "pmbus.h" -enum chips { max16508, max16601 }; +enum chips { max16508, max16601, max16602 }; #define REG_DEFAULT_NUM_POP 0xc4 #define REG_SETPT_DVID 0xd1 @@ -202,7 +202,7 @@ static int max16601_identify(struct i2c_client *client, else info->vrm_version[0] = vr12; - if (data->id != max16601) + if (data->id != max16601 && data->id != max16602) return 0; reg = i2c_smbus_read_byte_data(client, REG_DEFAULT_NUM_POP); @@ -264,6 +264,7 @@ static void max16601_remove(void *_data) static const struct i2c_device_id max16601_id[] = { {"max16508", max16508}, {"max16601", max16601}, + {"max16602", max16602}, {} }; MODULE_DEVICE_TABLE(i2c, max16601_id); @@ -280,13 +281,15 @@ static int max16601_get_id(struct i2c_client *client) return -ENODEV; /* - * PMBUS_IC_DEVICE_ID is expected to return "MAX16601y.xx" - * or "MAX16500y.xx". + * PMBUS_IC_DEVICE_ID is expected to return "MAX16601y.xx" or + * MAX16602y.xx or "MAX16500y.xx".cdxxcccccccccc */ if (!strncmp(buf, "MAX16500", 8)) { id = max16508; } else if (!strncmp(buf, "MAX16601", 8)) { id = max16601; + } else if (!strncmp(buf, "MAX16602", 8)) { + id = max16602; } else { buf[ret] = '\0'; dev_err(dev, "Unsupported chip '%s'\n", buf); diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index e74b6ef070f3..c031a9700ace 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -438,6 +438,8 @@ struct pmbus_driver_info { int (*read_byte_data)(struct i2c_client *client, int page, int reg); int (*read_word_data)(struct i2c_client *client, int page, int phase, int reg); + int (*write_byte_data)(struct i2c_client *client, int page, int reg, + u8 byte); int (*write_word_data)(struct i2c_client *client, int page, int reg, u16 word); int (*write_byte)(struct i2c_client *client, int page, u8 value); diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index d93574d6a1fb..02912022853d 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include "pmbus.h" /* @@ -276,6 +278,42 @@ static int _pmbus_write_word_data(struct i2c_client *client, int page, int reg, return pmbus_write_word_data(client, page, reg, word); } +/* + * _pmbus_write_byte_data() is similar to pmbus_write_byte_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_write_byte_data(struct i2c_client *client, int page, int reg, u8 value) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->write_byte_data) { + status = info->write_byte_data(client, page, reg, value); + if (status != -ENODATA) + return status; + } + return pmbus_write_byte_data(client, page, reg, value); +} + +/* + * _pmbus_read_byte_data() is similar to pmbus_read_byte_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->read_byte_data) { + status = info->read_byte_data(client, page, reg); + if (status != -ENODATA) + return status; + } + return pmbus_read_byte_data(client, page, reg); +} + int pmbus_update_fan(struct i2c_client *client, int page, int id, u8 config, u8 mask, u16 command) { @@ -283,14 +321,14 @@ int pmbus_update_fan(struct i2c_client *client, int page, int id, int rv; u8 to; - from = pmbus_read_byte_data(client, page, + from = _pmbus_read_byte_data(client, page, pmbus_fan_config_registers[id]); if (from < 0) return from; to = (from & ~mask) | (config & mask); if (to != from) { - rv = pmbus_write_byte_data(client, page, + rv = _pmbus_write_byte_data(client, page, pmbus_fan_config_registers[id], to); if (rv < 0) return rv; @@ -390,37 +428,19 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, unsigned int tmp; int rv; - rv = pmbus_read_byte_data(client, page, reg); + rv = _pmbus_read_byte_data(client, page, reg); if (rv < 0) return rv; tmp = (rv & ~mask) | (value & mask); if (tmp != rv) - rv = pmbus_write_byte_data(client, page, reg, tmp); + rv = _pmbus_write_byte_data(client, page, reg, tmp); return rv; } EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, PMBUS); -/* - * _pmbus_read_byte_data() is similar to pmbus_read_byte_data(), but checks if - * a device specific mapping function exists and calls it if necessary. - */ -static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg) -{ - struct pmbus_data *data = i2c_get_clientdata(client); - const struct pmbus_driver_info *info = data->info; - int status; - - if (info->read_byte_data) { - status = info->read_byte_data(client, page, reg); - if (status != -ENODATA) - return status; - } - return pmbus_read_byte_data(client, page, reg); -} - static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page, int reg) { @@ -455,7 +475,7 @@ static int pmbus_get_fan_rate(struct i2c_client *client, int page, int id, return s->data; } - config = pmbus_read_byte_data(client, page, + config = _pmbus_read_byte_data(client, page, pmbus_fan_config_registers[id]); if (config < 0) return config; @@ -912,7 +932,7 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b, regval = status & mask; if (regval) { - ret = pmbus_write_byte_data(client, page, reg, regval); + ret = _pmbus_write_byte_data(client, page, reg, regval); if (ret) goto unlock; } @@ -1083,6 +1103,68 @@ static int pmbus_add_boolean(struct pmbus_data *data, return pmbus_add_attribute(data, &a->dev_attr.attr); } +/* of thermal for pmbus temperature sensors */ +struct pmbus_thermal_data { + struct pmbus_data *pmbus_data; + struct pmbus_sensor *sensor; +}; + +static int pmbus_thermal_get_temp(void *data, int *temp) +{ + struct pmbus_thermal_data *tdata = data; + struct pmbus_sensor *sensor = tdata->sensor; + struct pmbus_data *pmbus_data = tdata->pmbus_data; + struct i2c_client *client = to_i2c_client(pmbus_data->dev); + struct device *dev = pmbus_data->hwmon_dev; + int ret = 0; + + if (!dev) { + /* May not even get to hwmon yet */ + *temp = 0; + return 0; + } + + mutex_lock(&pmbus_data->update_lock); + pmbus_update_sensor_data(client, sensor); + if (sensor->data < 0) + ret = sensor->data; + else + *temp = (int)pmbus_reg2data(pmbus_data, sensor); + mutex_unlock(&pmbus_data->update_lock); + + return ret; +} + +static const struct thermal_zone_of_device_ops pmbus_thermal_ops = { + .get_temp = pmbus_thermal_get_temp, +}; + +static int pmbus_thermal_add_sensor(struct pmbus_data *pmbus_data, + struct pmbus_sensor *sensor, int index) +{ + struct device *dev = pmbus_data->dev; + struct pmbus_thermal_data *tdata; + struct thermal_zone_device *tzd; + + tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL); + if (!tdata) + return -ENOMEM; + + tdata->sensor = sensor; + tdata->pmbus_data = pmbus_data; + + tzd = devm_thermal_zone_of_sensor_register(dev, index, tdata, + &pmbus_thermal_ops); + /* + * If CONFIG_THERMAL_OF is disabled, this returns -ENODEV, + * so ignore that error but forward any other error. + */ + if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV)) + return PTR_ERR(tzd); + + return 0; +} + static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data, const char *name, const char *type, int seq, int page, int phase, @@ -1126,6 +1208,10 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data, sensor->next = data->sensors; data->sensors = sensor; + /* temperature sensors with _input values are registered with thermal */ + if (class == PSC_TEMPERATURE && strcmp(type, "input") == 0) + pmbus_thermal_add_sensor(data, sensor, seq); + return sensor; } @@ -2308,6 +2394,21 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, struct device *dev = &client->dev; int page, ret; + /* + * Figure out if PEC is enabled before accessing any other register. + * Make sure PEC is disabled, will be enabled later if needed. + */ + client->flags &= ~I2C_CLIENT_PEC; + + /* Enable PEC if the controller and bus supports it */ + if (!(data->flags & PMBUS_NO_CAPABILITY)) { + ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY); + if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) { + if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC)) + client->flags |= I2C_CLIENT_PEC; + } + } + /* * Some PMBus chips don't support PMBUS_STATUS_WORD, so try * to use PMBUS_STATUS_BYTE instead if that is the case. @@ -2326,19 +2427,6 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, data->has_status_word = true; } - /* Make sure PEC is disabled, will be enabled later if needed */ - client->flags &= ~I2C_CLIENT_PEC; - - /* Enable PEC if the controller and bus supports it */ - if (!(data->flags & PMBUS_NO_CAPABILITY)) { - ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY); - if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) { - if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC)) { - client->flags |= I2C_CLIENT_PEC; - } - } - } - /* * Check if the chip is write protected. If it is, we can not clear * faults, and we should not try it. Also, in that case, writes into @@ -2399,7 +2487,7 @@ static int pmbus_regulator_is_enabled(struct regulator_dev *rdev) int ret; mutex_lock(&data->update_lock); - ret = pmbus_read_byte_data(client, page, PMBUS_OPERATION); + ret = _pmbus_read_byte_data(client, page, PMBUS_OPERATION); mutex_unlock(&data->update_lock); if (ret < 0) @@ -2498,7 +2586,7 @@ static int pmbus_regulator_get_error_flags(struct regulator_dev *rdev, unsigned if (!(func & cat->func)) continue; - status = pmbus_read_byte_data(client, page, cat->reg); + status = _pmbus_read_byte_data(client, page, cat->reg); if (status < 0) { mutex_unlock(&data->update_lock); return status; @@ -2548,11 +2636,78 @@ static int pmbus_regulator_get_error_flags(struct regulator_dev *rdev, unsigned return 0; } +static int pmbus_regulator_get_voltage(struct regulator_dev *rdev) +{ + struct device *dev = rdev_get_dev(rdev); + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); + struct pmbus_sensor s = { + .page = rdev_get_id(rdev), + .class = PSC_VOLTAGE_OUT, + .convert = true, + }; + + s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_READ_VOUT); + if (s.data < 0) + return s.data; + + return (int)pmbus_reg2data(data, &s) * 1000; /* unit is uV */ +} + +static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv, + int max_uv, unsigned int *selector) +{ + struct device *dev = rdev_get_dev(rdev); + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); + struct pmbus_sensor s = { + .page = rdev_get_id(rdev), + .class = PSC_VOLTAGE_OUT, + .convert = true, + .data = -1, + }; + int val = DIV_ROUND_CLOSEST(min_uv, 1000); /* convert to mV */ + int low, high; + + *selector = 0; + + if (pmbus_check_word_register(client, s.page, PMBUS_MFR_VOUT_MIN)) + s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_MFR_VOUT_MIN); + if (s.data < 0) { + s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_VOUT_MARGIN_LOW); + if (s.data < 0) + return s.data; + } + low = pmbus_reg2data(data, &s); + + s.data = -1; + if (pmbus_check_word_register(client, s.page, PMBUS_MFR_VOUT_MAX)) + s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_MFR_VOUT_MAX); + if (s.data < 0) { + s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_VOUT_MARGIN_HIGH); + if (s.data < 0) + return s.data; + } + high = pmbus_reg2data(data, &s); + + /* Make sure we are within margins */ + if (low > val) + val = low; + if (high < val) + val = high; + + val = pmbus_data2reg(data, &s, val); + + return _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val); +} + const struct regulator_ops pmbus_regulator_ops = { .enable = pmbus_regulator_enable, .disable = pmbus_regulator_disable, .is_enabled = pmbus_regulator_is_enabled, .get_error_flags = pmbus_regulator_get_error_flags, + .get_voltage = pmbus_regulator_get_voltage, + .set_voltage = pmbus_regulator_set_voltage, }; EXPORT_SYMBOL_NS_GPL(pmbus_regulator_ops, PMBUS); diff --git a/drivers/hwmon/pmbus/xdpe152c4.c b/drivers/hwmon/pmbus/xdpe152c4.c new file mode 100644 index 000000000000..b8a36ef73e45 --- /dev/null +++ b/drivers/hwmon/pmbus/xdpe152c4.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for Infineon Multi-phase Digital VR Controllers + * + * Copyright (c) 2022 Infineon Technologies. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include "pmbus.h" + +#define XDPE152_PAGE_NUM 2 + +static struct pmbus_driver_info xdpe152_info = { + .pages = XDPE152_PAGE_NUM, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = linear, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, + .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, +}; + +static int xdpe152_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + + info = devm_kmemdup(&client->dev, &xdpe152_info, sizeof(*info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id xdpe152_id[] = { + {"xdpe152c4", 0}, + {"xdpe15284", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, xdpe152_id); + +static const struct of_device_id __maybe_unused xdpe152_of_match[] = { + {.compatible = "infineon,xdpe152c4"}, + {.compatible = "infineon,xdpe15284"}, + {} +}; +MODULE_DEVICE_TABLE(of, xdpe152_of_match); + +static struct i2c_driver xdpe152_driver = { + .driver = { + .name = "xdpe152c4", + .of_match_table = of_match_ptr(xdpe152_of_match), + }, + .probe_new = xdpe152_probe, + .id_table = xdpe152_id, +}; + +module_i2c_driver(xdpe152_driver); + +MODULE_AUTHOR("Greg Schwendimann "); +MODULE_DESCRIPTION("PMBus driver for Infineon XDPE152 family"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index f12b9a28a232..6c08551d8d14 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -49,16 +49,6 @@ struct pwm_fan_ctx { struct hwmon_channel_info fan_channel; }; -static const u32 pwm_fan_channel_config_pwm[] = { - HWMON_PWM_INPUT, - 0 -}; - -static const struct hwmon_channel_info pwm_fan_channel_pwm = { - .type = hwmon_pwm, - .config = pwm_fan_channel_config_pwm, -}; - /* This handler assumes self resetting edge triggered interrupt. */ static irqreturn_t pulse_handler(int irq, void *dev_id) { @@ -387,7 +377,7 @@ static int pwm_fan_probe(struct platform_device *pdev) if (!channels) return -ENOMEM; - channels[0] = &pwm_fan_channel_pwm; + channels[0] = HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT); for (i = 0; i < ctx->tach_count; i++) { struct pwm_fan_tach *tach = &ctx->tachs[i]; diff --git a/drivers/hwmon/sl28cpld-hwmon.c b/drivers/hwmon/sl28cpld-hwmon.c index e48f58ec5b9c..9ce4899a81a5 100644 --- a/drivers/hwmon/sl28cpld-hwmon.c +++ b/drivers/hwmon/sl28cpld-hwmon.c @@ -54,7 +54,7 @@ static int sl28cpld_hwmon_read(struct device *dev, /* * The counter period is 1000ms and the sysfs specification - * says we should asssume 2 pulses per revolution. + * says we should assume 2 pulses per revolution. */ value *= 60 / 2; @@ -67,18 +67,8 @@ static int sl28cpld_hwmon_read(struct device *dev, return 0; } -static const u32 sl28cpld_hwmon_fan_config[] = { - HWMON_F_INPUT, - 0 -}; - -static const struct hwmon_channel_info sl28cpld_hwmon_fan = { - .type = hwmon_fan, - .config = sl28cpld_hwmon_fan_config, -}; - static const struct hwmon_channel_info *sl28cpld_hwmon_info[] = { - &sl28cpld_hwmon_fan, + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), NULL }; diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index 52c9e7d3f2ae..cc0a1c219b1f 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -41,6 +41,8 @@ enum chips { tmp401, tmp411, tmp431, tmp432, tmp435 }; #define TMP401_STATUS 0x02 #define TMP401_CONFIG 0x03 #define TMP401_CONVERSION_RATE 0x04 +#define TMP4XX_N_FACTOR_REG 0x18 +#define TMP43X_BETA_RANGE 0x25 #define TMP401_TEMP_CRIT_HYST 0x21 #define TMP401_MANUFACTURER_ID_REG 0xFE #define TMP401_DEVICE_ID_REG 0xFF @@ -543,6 +545,8 @@ static int tmp401_init_client(struct tmp401_data *data) struct regmap *regmap = data->regmap; u32 config, config_orig; int ret; + u32 val = 0; + s32 nfactor = 0; /* Set conversion rate to 2 Hz */ ret = regmap_write(regmap, TMP401_CONVERSION_RATE, 5); @@ -557,12 +561,50 @@ static int tmp401_init_client(struct tmp401_data *data) config_orig = config; config &= ~TMP401_CONFIG_SHUTDOWN; + if (of_property_read_bool(data->client->dev.of_node, "ti,extended-range-enable")) { + /* Enable measurement over extended temperature range */ + config |= TMP401_CONFIG_RANGE; + } + data->extended_range = !!(config & TMP401_CONFIG_RANGE); - if (config != config_orig) + if (config != config_orig) { ret = regmap_write(regmap, TMP401_CONFIG, config); + if (ret < 0) + return ret; + } - return ret; + ret = of_property_read_u32(data->client->dev.of_node, "ti,n-factor", &nfactor); + if (!ret) { + if (data->kind == tmp401) { + dev_err(&data->client->dev, "ti,tmp401 does not support n-factor correction\n"); + return -EINVAL; + } + if (nfactor < -128 || nfactor > 127) { + dev_err(&data->client->dev, "n-factor is invalid (%d)\n", nfactor); + return -EINVAL; + } + ret = regmap_write(regmap, TMP4XX_N_FACTOR_REG, (unsigned int)nfactor); + if (ret < 0) + return ret; + } + + ret = of_property_read_u32(data->client->dev.of_node, "ti,beta-compensation", &val); + if (!ret) { + if (data->kind == tmp401 || data->kind == tmp411) { + dev_err(&data->client->dev, "ti,tmp401 or ti,tmp411 does not support beta compensation\n"); + return -EINVAL; + } + if (val > 15) { + dev_err(&data->client->dev, "beta-compensation is invalid (%u)\n", val); + return -EINVAL; + } + ret = regmap_write(regmap, TMP43X_BETA_RANGE, val); + if (ret < 0) + return ret; + } + + return 0; } static int tmp401_detect(struct i2c_client *client, diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c index 47551ab73ca8..b9bb94bd0f67 100644 --- a/drivers/idle/intel_idle.c +++ b/drivers/idle/intel_idle.c @@ -764,6 +764,106 @@ static struct cpuidle_state icx_cstates[] __initdata = { .enter = NULL } }; +/* + * On AlderLake C1 has to be disabled if C1E is enabled, and vice versa. + * C1E is enabled only if "C1E promotion" bit is set in MSR_IA32_POWER_CTL. + * But in this case there is effectively no C1, because C1 requests are + * promoted to C1E. If the "C1E promotion" bit is cleared, then both C1 + * and C1E requests end up with C1, so there is effectively no C1E. + * + * By default we enable C1E and disable C1 by marking it with + * 'CPUIDLE_FLAG_UNUSABLE'. + */ +static struct cpuidle_state adl_cstates[] __initdata = { + { + .name = "C1", + .desc = "MWAIT 0x00", + .flags = MWAIT2flg(0x00) | CPUIDLE_FLAG_UNUSABLE, + .exit_latency = 1, + .target_residency = 1, + .enter = &intel_idle, + .enter_s2idle = intel_idle_s2idle, }, + { + .name = "C1E", + .desc = "MWAIT 0x01", + .flags = MWAIT2flg(0x01) | CPUIDLE_FLAG_ALWAYS_ENABLE, + .exit_latency = 2, + .target_residency = 4, + .enter = &intel_idle, + .enter_s2idle = intel_idle_s2idle, }, + { + .name = "C6", + .desc = "MWAIT 0x20", + .flags = MWAIT2flg(0x20) | CPUIDLE_FLAG_TLB_FLUSHED, + .exit_latency = 220, + .target_residency = 600, + .enter = &intel_idle, + .enter_s2idle = intel_idle_s2idle, }, + { + .name = "C8", + .desc = "MWAIT 0x40", + .flags = MWAIT2flg(0x40) | CPUIDLE_FLAG_TLB_FLUSHED, + .exit_latency = 280, + .target_residency = 800, + .enter = &intel_idle, + .enter_s2idle = intel_idle_s2idle, }, + { + .name = "C10", + .desc = "MWAIT 0x60", + .flags = MWAIT2flg(0x60) | CPUIDLE_FLAG_TLB_FLUSHED, + .exit_latency = 680, + .target_residency = 2000, + .enter = &intel_idle, + .enter_s2idle = intel_idle_s2idle, }, + { + .enter = NULL } +}; + +static struct cpuidle_state adl_l_cstates[] __initdata = { + { + .name = "C1", + .desc = "MWAIT 0x00", + .flags = MWAIT2flg(0x00) | CPUIDLE_FLAG_UNUSABLE, + .exit_latency = 1, + .target_residency = 1, + .enter = &intel_idle, + .enter_s2idle = intel_idle_s2idle, }, + { + .name = "C1E", + .desc = "MWAIT 0x01", + .flags = MWAIT2flg(0x01) | CPUIDLE_FLAG_ALWAYS_ENABLE, + .exit_latency = 2, + .target_residency = 4, + .enter = &intel_idle, + .enter_s2idle = intel_idle_s2idle, }, + { + .name = "C6", + .desc = "MWAIT 0x20", + .flags = MWAIT2flg(0x20) | CPUIDLE_FLAG_TLB_FLUSHED, + .exit_latency = 170, + .target_residency = 500, + .enter = &intel_idle, + .enter_s2idle = intel_idle_s2idle, }, + { + .name = "C8", + .desc = "MWAIT 0x40", + .flags = MWAIT2flg(0x40) | CPUIDLE_FLAG_TLB_FLUSHED, + .exit_latency = 200, + .target_residency = 600, + .enter = &intel_idle, + .enter_s2idle = intel_idle_s2idle, }, + { + .name = "C10", + .desc = "MWAIT 0x60", + .flags = MWAIT2flg(0x60) | CPUIDLE_FLAG_TLB_FLUSHED, + .exit_latency = 230, + .target_residency = 700, + .enter = &intel_idle, + .enter_s2idle = intel_idle_s2idle, }, + { + .enter = NULL } +}; + /* * On Sapphire Rapids Xeon C1 has to be disabled if C1E is enabled, and vice * versa. On SPR C1E is enabled only if "C1E promotion" bit is set in @@ -1147,6 +1247,14 @@ static const struct idle_cpu idle_cpu_icx __initconst = { .use_acpi = true, }; +static const struct idle_cpu idle_cpu_adl __initconst = { + .state_table = adl_cstates, +}; + +static const struct idle_cpu idle_cpu_adl_l __initconst = { + .state_table = adl_l_cstates, +}; + static const struct idle_cpu idle_cpu_spr __initconst = { .state_table = spr_cstates, .disable_promotion_to_c1e = true, @@ -1215,6 +1323,8 @@ static const struct x86_cpu_id intel_idle_ids[] __initconst = { X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, &idle_cpu_skx), X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_X, &idle_cpu_icx), X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, &idle_cpu_icx), + X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE, &idle_cpu_adl), + X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, &idle_cpu_adl_l), X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, &idle_cpu_spr), X86_MATCH_INTEL_FAM6_MODEL(XEON_PHI_KNL, &idle_cpu_knl), X86_MATCH_INTEL_FAM6_MODEL(XEON_PHI_KNM, &idle_cpu_knl), @@ -1573,6 +1683,25 @@ static void __init skx_idle_state_table_update(void) } } +/** + * adl_idle_state_table_update - Adjust AlderLake idle states table. + */ +static void __init adl_idle_state_table_update(void) +{ + /* Check if user prefers C1 over C1E. */ + if (preferred_states_mask & BIT(1) && !(preferred_states_mask & BIT(2))) { + cpuidle_state_table[0].flags &= ~CPUIDLE_FLAG_UNUSABLE; + cpuidle_state_table[1].flags |= CPUIDLE_FLAG_UNUSABLE; + + /* Disable C1E by clearing the "C1E promotion" bit. */ + c1e_promotion = C1E_PROMOTION_DISABLE; + return; + } + + /* Make sure C1E is enabled by default */ + c1e_promotion = C1E_PROMOTION_ENABLE; +} + /** * spr_idle_state_table_update - Adjust Sapphire Rapids idle states table. */ @@ -1642,6 +1771,10 @@ static void __init intel_idle_init_cstates_icpu(struct cpuidle_driver *drv) case INTEL_FAM6_SAPPHIRERAPIDS_X: spr_idle_state_table_update(); break; + case INTEL_FAM6_ALDERLAKE: + case INTEL_FAM6_ALDERLAKE_L: + adl_idle_state_table_update(); + break; } for (cstate = 0; cstate < CPUIDLE_STATE_MAX; ++cstate) { diff --git a/drivers/iio/chemical/scd30.h b/drivers/iio/chemical/scd30.h index f60127bfe0f4..1ac9f3f79271 100644 --- a/drivers/iio/chemical/scd30.h +++ b/drivers/iio/chemical/scd30.h @@ -68,10 +68,7 @@ struct scd30_state { scd30_command_t command; }; -int scd30_suspend(struct device *dev); -int scd30_resume(struct device *dev); - -static __maybe_unused SIMPLE_DEV_PM_OPS(scd30_pm_ops, scd30_suspend, scd30_resume); +extern const struct dev_pm_ops scd30_pm_ops; int scd30_probe(struct device *dev, int irq, const char *name, void *priv, scd30_command_t command); diff --git a/drivers/iio/chemical/scd30_core.c b/drivers/iio/chemical/scd30_core.c index 9fe6bbe9ee04..682fca39d14d 100644 --- a/drivers/iio/chemical/scd30_core.c +++ b/drivers/iio/chemical/scd30_core.c @@ -517,7 +517,7 @@ static const struct iio_chan_spec scd30_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(3), }; -int __maybe_unused scd30_suspend(struct device *dev) +static int scd30_suspend(struct device *dev) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct scd30_state *state = iio_priv(indio_dev); @@ -529,9 +529,8 @@ int __maybe_unused scd30_suspend(struct device *dev) return regulator_disable(state->vdd); } -EXPORT_SYMBOL(scd30_suspend); -int __maybe_unused scd30_resume(struct device *dev) +static int scd30_resume(struct device *dev) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct scd30_state *state = iio_priv(indio_dev); @@ -543,7 +542,8 @@ int __maybe_unused scd30_resume(struct device *dev) return scd30_command_write(state, CMD_START_MEAS, state->pressure_comp); } -EXPORT_SYMBOL(scd30_resume); + +EXPORT_NS_SIMPLE_DEV_PM_OPS(scd30_pm_ops, scd30_suspend, scd30_resume, IIO_SCD30); static void scd30_stop_meas(void *data) { @@ -759,7 +759,7 @@ int scd30_probe(struct device *dev, int irq, const char *name, void *priv, return devm_iio_device_register(dev, indio_dev); } -EXPORT_SYMBOL(scd30_probe); +EXPORT_SYMBOL_NS(scd30_probe, IIO_SCD30); MODULE_AUTHOR("Tomasz Duszynski "); MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor core driver"); diff --git a/drivers/iio/chemical/scd30_i2c.c b/drivers/iio/chemical/scd30_i2c.c index 875892a070ee..bae479a4721f 100644 --- a/drivers/iio/chemical/scd30_i2c.c +++ b/drivers/iio/chemical/scd30_i2c.c @@ -128,7 +128,7 @@ static struct i2c_driver scd30_i2c_driver = { .driver = { .name = KBUILD_MODNAME, .of_match_table = scd30_i2c_of_match, - .pm = &scd30_pm_ops, + .pm = pm_sleep_ptr(&scd30_pm_ops), }, .probe_new = scd30_i2c_probe, }; @@ -137,3 +137,4 @@ module_i2c_driver(scd30_i2c_driver); MODULE_AUTHOR("Tomasz Duszynski "); MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor i2c driver"); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS(IIO_SCD30); diff --git a/drivers/iio/chemical/scd30_serial.c b/drivers/iio/chemical/scd30_serial.c index 568b34486c44..3c519103d30b 100644 --- a/drivers/iio/chemical/scd30_serial.c +++ b/drivers/iio/chemical/scd30_serial.c @@ -252,7 +252,7 @@ static struct serdev_device_driver scd30_serdev_driver = { .driver = { .name = KBUILD_MODNAME, .of_match_table = scd30_serdev_of_match, - .pm = &scd30_pm_ops, + .pm = pm_sleep_ptr(&scd30_pm_ops), }, .probe = scd30_serdev_probe, }; @@ -261,3 +261,4 @@ module_serdev_device_driver(scd30_serdev_driver); MODULE_AUTHOR("Tomasz Duszynski "); MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor serial driver"); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS(IIO_SCD30); diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index 506dc900f5c7..1259ca22d625 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -126,6 +126,7 @@ struct mmc_blk_data { #define MMC_BLK_DISCARD BIT(2) #define MMC_BLK_SECDISCARD BIT(3) #define MMC_BLK_CQE_RECOVERY BIT(4) +#define MMC_BLK_TRIM BIT(5) /* * Only set in main mmc_blk_data associated @@ -330,7 +331,7 @@ static struct attribute *mmc_disk_attrs[] = { static umode_t mmc_disk_attrs_is_visible(struct kobject *kobj, struct attribute *a, int n) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); umode_t mode = a->mode; @@ -609,11 +610,11 @@ static int __mmc_blk_ioctl_cmd(struct mmc_card *card, struct mmc_blk_data *md, if (idata->rpmb || (cmd.flags & MMC_RSP_R1B) == MMC_RSP_R1B) { /* - * Ensure RPMB/R1B command has completed by polling CMD13 - * "Send Status". + * Ensure RPMB/R1B command has completed by polling CMD13 "Send Status". Here we + * allow to override the default timeout value if a custom timeout is specified. */ - err = mmc_poll_for_busy(card, MMC_BLK_TIMEOUT_MS, false, - MMC_BUSY_IO); + err = mmc_poll_for_busy(card, idata->ic.cmd_timeout_ms ? : MMC_BLK_TIMEOUT_MS, + false, MMC_BUSY_IO); } return err; @@ -676,8 +677,9 @@ static int mmc_blk_ioctl_multi_cmd(struct mmc_blk_data *md, struct mmc_ioc_cmd __user *cmds = user->cmds; struct mmc_card *card; struct mmc_queue *mq; - int i, err = 0, ioc_err = 0; + int err = 0, ioc_err = 0; __u64 num_of_cmds; + unsigned int i, n; struct request *req; if (copy_from_user(&num_of_cmds, &user->num_of_cmds, @@ -690,15 +692,16 @@ static int mmc_blk_ioctl_multi_cmd(struct mmc_blk_data *md, if (num_of_cmds > MMC_IOC_MAX_CMDS) return -EINVAL; - idata = kcalloc(num_of_cmds, sizeof(*idata), GFP_KERNEL); + n = num_of_cmds; + idata = kcalloc(n, sizeof(*idata), GFP_KERNEL); if (!idata) return -ENOMEM; - for (i = 0; i < num_of_cmds; i++) { + for (i = 0; i < n; i++) { idata[i] = mmc_blk_ioctl_copy_from_user(&cmds[i]); if (IS_ERR(idata[i])) { err = PTR_ERR(idata[i]); - num_of_cmds = i; + n = i; goto cmd_err; } /* This will be NULL on non-RPMB ioctl():s */ @@ -725,18 +728,18 @@ static int mmc_blk_ioctl_multi_cmd(struct mmc_blk_data *md, req_to_mmc_queue_req(req)->drv_op = rpmb ? MMC_DRV_OP_IOCTL_RPMB : MMC_DRV_OP_IOCTL; req_to_mmc_queue_req(req)->drv_op_data = idata; - req_to_mmc_queue_req(req)->ioc_count = num_of_cmds; + req_to_mmc_queue_req(req)->ioc_count = n; blk_execute_rq(req, false); ioc_err = req_to_mmc_queue_req(req)->drv_op_result; /* copy to user if data and response */ - for (i = 0; i < num_of_cmds && !err; i++) + for (i = 0; i < n && !err; i++) err = mmc_blk_ioctl_copy_to_user(&cmds[i], idata[i]); blk_mq_free_request(req); cmd_err: - for (i = 0; i < num_of_cmds; i++) { + for (i = 0; i < n; i++) { kfree(idata[i]->buf); kfree(idata[i]); } @@ -1090,12 +1093,13 @@ static void mmc_blk_issue_drv_op(struct mmc_queue *mq, struct request *req) blk_mq_end_request(req, ret ? BLK_STS_IOERR : BLK_STS_OK); } -static void mmc_blk_issue_discard_rq(struct mmc_queue *mq, struct request *req) +static void mmc_blk_issue_erase_rq(struct mmc_queue *mq, struct request *req, + int type, unsigned int erase_arg) { struct mmc_blk_data *md = mq->blkdata; struct mmc_card *card = md->queue.card; unsigned int from, nr; - int err = 0, type = MMC_BLK_DISCARD; + int err = 0; blk_status_t status = BLK_STS_OK; if (!mmc_can_erase(card)) { @@ -1111,13 +1115,13 @@ static void mmc_blk_issue_discard_rq(struct mmc_queue *mq, struct request *req) if (card->quirks & MMC_QUIRK_INAND_CMD38) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, INAND_CMD38_ARG_EXT_CSD, - card->erase_arg == MMC_TRIM_ARG ? + erase_arg == MMC_TRIM_ARG ? INAND_CMD38_ARG_TRIM : INAND_CMD38_ARG_ERASE, card->ext_csd.generic_cmd6_time); } if (!err) - err = mmc_erase(card, from, nr, card->erase_arg); + err = mmc_erase(card, from, nr, erase_arg); } while (err == -EIO && !mmc_blk_reset(md, card->host, type)); if (err) status = BLK_STS_IOERR; @@ -1127,6 +1131,19 @@ fail: blk_mq_end_request(req, status); } +static void mmc_blk_issue_trim_rq(struct mmc_queue *mq, struct request *req) +{ + mmc_blk_issue_erase_rq(mq, req, MMC_BLK_TRIM, MMC_TRIM_ARG); +} + +static void mmc_blk_issue_discard_rq(struct mmc_queue *mq, struct request *req) +{ + struct mmc_blk_data *md = mq->blkdata; + struct mmc_card *card = md->queue.card; + + mmc_blk_issue_erase_rq(mq, req, MMC_BLK_DISCARD, card->erase_arg); +} + static void mmc_blk_issue_secdiscard_rq(struct mmc_queue *mq, struct request *req) { @@ -2327,6 +2344,9 @@ enum mmc_issued mmc_blk_mq_issue_rq(struct mmc_queue *mq, struct request *req) case REQ_OP_SECURE_ERASE: mmc_blk_issue_secdiscard_rq(mq, req); break; + case REQ_OP_WRITE_ZEROES: + mmc_blk_issue_trim_rq(mq, req); + break; case REQ_OP_FLUSH: mmc_blk_issue_flush(mq, req); break; diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index c6ae16d40766..4b70cbfc6d5d 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1988,9 +1988,9 @@ static void mmc_hw_reset_for_init(struct mmc_host *host) { mmc_pwrseq_reset(host); - if (!(host->caps & MMC_CAP_HW_RESET) || !host->ops->hw_reset) + if (!(host->caps & MMC_CAP_HW_RESET) || !host->ops->card_hw_reset) return; - host->ops->hw_reset(host); + host->ops->card_hw_reset(host); } /** @@ -2017,8 +2017,9 @@ int mmc_hw_reset(struct mmc_card *card) } EXPORT_SYMBOL(mmc_hw_reset); -int mmc_sw_reset(struct mmc_host *host) +int mmc_sw_reset(struct mmc_card *card) { + struct mmc_host *host = card->host; int ret; if (!host->bus_ops->sw_reset) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index efa95dc4fc4e..89cd48fcec79 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -71,6 +72,12 @@ static int mmc_decode_cid(struct mmc_card *card) { u32 *resp = card->raw_cid; + /* + * Add the raw card ID (cid) data to the entropy pool. It doesn't + * matter that not all of it is unique, it's just bonus entropy. + */ + add_device_randomness(&card->raw_cid, sizeof(card->raw_cid)); + /* * The selection of the format here is based upon published * specs from sandisk and from what people have reported. @@ -2240,11 +2247,11 @@ static int _mmc_hw_reset(struct mmc_host *host) */ _mmc_flush_cache(host); - if ((host->caps & MMC_CAP_HW_RESET) && host->ops->hw_reset && + if ((host->caps & MMC_CAP_HW_RESET) && host->ops->card_hw_reset && mmc_can_reset(card)) { /* If the card accept RST_n signal, send it. */ mmc_set_clock(host, host->f_init); - host->ops->hw_reset(host); + host->ops->card_hw_reset(host); /* Set initial state and call mmc_set_ios */ mmc_set_initial_state(host); } else { diff --git a/drivers/mmc/core/queue.c b/drivers/mmc/core/queue.c index a3d446005571..fa5324ceeebe 100644 --- a/drivers/mmc/core/queue.c +++ b/drivers/mmc/core/queue.c @@ -190,6 +190,8 @@ static void mmc_queue_setup_discard(struct request_queue *q, q->limits.discard_granularity = SECTOR_SIZE; if (mmc_can_secure_erase_trim(card)) blk_queue_max_secure_erase_sectors(q, max_discard); + if (mmc_can_trim(card) && card->erased_byte == 0) + blk_queue_max_write_zeroes_sectors(q, max_discard); } static unsigned short mmc_get_max_segments(struct mmc_host *host) diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 68df6b2f49cc..c5f1df6ce4c0 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -83,6 +84,12 @@ void mmc_decode_cid(struct mmc_card *card) { u32 *resp = card->raw_cid; + /* + * Add the raw card ID (cid) data to the entropy pool. It doesn't + * matter that not all of it is unique, it's just bonus entropy. + */ + add_device_randomness(&card->raw_cid, sizeof(card->raw_cid)); + /* * SD doesn't currently have a version field so we will * have to assume we can parse this. diff --git a/drivers/mmc/host/atmel-mci.c b/drivers/mmc/host/atmel-mci.c index 807177c953f3..91d52ba7a39f 100644 --- a/drivers/mmc/host/atmel-mci.c +++ b/drivers/mmc/host/atmel-mci.c @@ -1122,13 +1122,12 @@ atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data) } /* If we don't have a channel, we can't do DMA */ - chan = host->dma.chan; - if (chan) - host->data_chan = chan; - - if (!chan) + if (!host->dma.chan) return -ENODEV; + chan = host->dma.chan; + host->data_chan = chan; + if (data->flags & MMC_DATA_READ) { host->dma_conf.direction = slave_dirn = DMA_DEV_TO_MEM; maxburst = atmci_convert_chksize(host, diff --git a/drivers/mmc/host/bcm2835.c b/drivers/mmc/host/bcm2835.c index 463b707d9e99..641ab4f42125 100644 --- a/drivers/mmc/host/bcm2835.c +++ b/drivers/mmc/host/bcm2835.c @@ -1259,7 +1259,7 @@ static void bcm2835_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) static const struct mmc_host_ops bcm2835_ops = { .request = bcm2835_request, .set_ios = bcm2835_set_ios, - .hw_reset = bcm2835_reset, + .card_hw_reset = bcm2835_reset, }; static int bcm2835_add_host(struct bcm2835_host *host) diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 06dc56cbada8..581614196a84 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -1812,7 +1812,7 @@ static const struct mmc_host_ops dw_mci_ops = { .set_ios = dw_mci_set_ios, .get_ro = dw_mci_get_ro, .get_cd = dw_mci_get_cd, - .hw_reset = dw_mci_hw_reset, + .card_hw_reset = dw_mci_hw_reset, .enable_sdio_irq = dw_mci_enable_sdio_irq, .ack_sdio_irq = dw_mci_ack_sdio_irq, .execute_tuning = dw_mci_execute_tuning, diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c index 7ab1b38a7be5..b1d563b2ed1b 100644 --- a/drivers/mmc/host/jz4740_mmc.c +++ b/drivers/mmc/host/jz4740_mmc.c @@ -247,6 +247,26 @@ static int jz4740_mmc_acquire_dma_channels(struct jz4740_mmc_host *host) return PTR_ERR(host->dma_rx); } + /* + * Limit the maximum segment size in any SG entry according to + * the parameters of the DMA engine device. + */ + if (host->dma_tx) { + struct device *dev = host->dma_tx->device->dev; + unsigned int max_seg_size = dma_get_max_seg_size(dev); + + if (max_seg_size < host->mmc->max_seg_size) + host->mmc->max_seg_size = max_seg_size; + } + + if (host->dma_rx) { + struct device *dev = host->dma_rx->device->dev; + unsigned int max_seg_size = dma_get_max_seg_size(dev); + + if (max_seg_size < host->mmc->max_seg_size) + host->mmc->max_seg_size = max_seg_size; + } + return 0; } diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 58ab9d90bc8b..2f08d442e557 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -1271,8 +1271,8 @@ static int meson_mmc_probe(struct platform_device *pdev) /* data bounce buffer */ host->bounce_buf_size = mmc->max_req_size; host->bounce_buf = - dma_alloc_coherent(host->dev, host->bounce_buf_size, - &host->bounce_dma_addr, GFP_KERNEL); + dmam_alloc_coherent(host->dev, host->bounce_buf_size, + &host->bounce_dma_addr, GFP_KERNEL); if (host->bounce_buf == NULL) { dev_err(host->dev, "Unable to map allocate DMA bounce buffer.\n"); ret = -ENOMEM; @@ -1280,12 +1280,12 @@ static int meson_mmc_probe(struct platform_device *pdev) } } - host->descs = dma_alloc_coherent(host->dev, SD_EMMC_DESC_BUF_LEN, - &host->descs_dma_addr, GFP_KERNEL); + host->descs = dmam_alloc_coherent(host->dev, SD_EMMC_DESC_BUF_LEN, + &host->descs_dma_addr, GFP_KERNEL); if (!host->descs) { dev_err(host->dev, "Allocating descriptor DMA buffer failed\n"); ret = -ENOMEM; - goto err_bounce_buf; + goto err_free_irq; } mmc->ops = &meson_mmc_ops; @@ -1293,10 +1293,6 @@ static int meson_mmc_probe(struct platform_device *pdev) return 0; -err_bounce_buf: - if (!host->dram_access_quirk) - dma_free_coherent(host->dev, host->bounce_buf_size, - host->bounce_buf, host->bounce_dma_addr); err_free_irq: free_irq(host->irq, host); err_init_clk: @@ -1318,13 +1314,6 @@ static int meson_mmc_remove(struct platform_device *pdev) writel(0, host->regs + SD_EMMC_IRQ_EN); free_irq(host->irq, host); - dma_free_coherent(host->dev, SD_EMMC_DESC_BUF_LEN, - host->descs, host->descs_dma_addr); - - if (!host->dram_access_quirk) - dma_free_coherent(host->dev, host->bounce_buf_size, - host->bounce_buf, host->bounce_dma_addr); - clk_disable_unprepare(host->mmc_clk); clk_disable_unprepare(host->core_clk); diff --git a/drivers/mmc/host/meson-mx-sdhc-mmc.c b/drivers/mmc/host/meson-mx-sdhc-mmc.c index 28aa78aa08f3..e92e63cb5641 100644 --- a/drivers/mmc/host/meson-mx-sdhc-mmc.c +++ b/drivers/mmc/host/meson-mx-sdhc-mmc.c @@ -511,7 +511,7 @@ static int meson_mx_sdhc_execute_tuning(struct mmc_host *mmc, u32 opcode) } static const struct mmc_host_ops meson_mx_sdhc_ops = { - .hw_reset = meson_mx_sdhc_hw_reset, + .card_hw_reset = meson_mx_sdhc_hw_reset, .request = meson_mx_sdhc_request, .set_ios = meson_mx_sdhc_set_ios, .card_busy = meson_mx_sdhc_card_busy, diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 45b8608c935c..01159eaf8694 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -1619,6 +1619,8 @@ static irqreturn_t mmci_irq(int irq, void *dev_id) do { status = readl(host->base + MMCISTATUS); + if (!status) + break; if (host->singleirq) { if (status & host->mask1_reg) @@ -1746,10 +1748,6 @@ static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) unsigned long flags; int ret; - if (host->plat->ios_handler && - host->plat->ios_handler(mmc_dev(mmc), ios)) - dev_err(mmc_dev(mmc), "platform ios_handler failed\n"); - switch (ios->power_mode) { case MMC_POWER_OFF: if (!IS_ERR(mmc->supply.vmmc)) diff --git a/drivers/mmc/host/mmci_stm32_sdmmc.c b/drivers/mmc/host/mmci_stm32_sdmmc.c index 4566d7fc9055..60bca78a72b1 100644 --- a/drivers/mmc/host/mmci_stm32_sdmmc.c +++ b/drivers/mmc/host/mmci_stm32_sdmmc.c @@ -43,6 +43,9 @@ struct sdmmc_lli_desc { struct sdmmc_idma { dma_addr_t sg_dma; void *sg_cpu; + dma_addr_t bounce_dma_addr; + void *bounce_buf; + bool use_bounce_buffer; }; struct sdmmc_dlyb { @@ -54,6 +57,8 @@ struct sdmmc_dlyb { static int sdmmc_idma_validate_data(struct mmci_host *host, struct mmc_data *data) { + struct sdmmc_idma *idma = host->dma_priv; + struct device *dev = mmc_dev(host->mmc); struct scatterlist *sg; int i; @@ -61,41 +66,69 @@ static int sdmmc_idma_validate_data(struct mmci_host *host, * idma has constraints on idmabase & idmasize for each element * excepted the last element which has no constraint on idmasize */ + idma->use_bounce_buffer = false; for_each_sg(data->sg, sg, data->sg_len - 1, i) { if (!IS_ALIGNED(sg->offset, sizeof(u32)) || !IS_ALIGNED(sg->length, SDMMC_IDMA_BURST)) { - dev_err(mmc_dev(host->mmc), + dev_dbg(mmc_dev(host->mmc), "unaligned scatterlist: ofst:%x length:%d\n", data->sg->offset, data->sg->length); - return -EINVAL; + goto use_bounce_buffer; } } if (!IS_ALIGNED(sg->offset, sizeof(u32))) { - dev_err(mmc_dev(host->mmc), + dev_dbg(mmc_dev(host->mmc), "unaligned last scatterlist: ofst:%x length:%d\n", data->sg->offset, data->sg->length); - return -EINVAL; + goto use_bounce_buffer; } + return 0; + +use_bounce_buffer: + if (!idma->bounce_buf) { + idma->bounce_buf = dmam_alloc_coherent(dev, + host->mmc->max_req_size, + &idma->bounce_dma_addr, + GFP_KERNEL); + if (!idma->bounce_buf) { + dev_err(dev, "Unable to map allocate DMA bounce buffer.\n"); + return -ENOMEM; + } + } + + idma->use_bounce_buffer = true; + return 0; } static int _sdmmc_idma_prep_data(struct mmci_host *host, struct mmc_data *data) { - int n_elem; + struct sdmmc_idma *idma = host->dma_priv; - n_elem = dma_map_sg(mmc_dev(host->mmc), - data->sg, - data->sg_len, - mmc_get_dma_dir(data)); + if (idma->use_bounce_buffer) { + if (data->flags & MMC_DATA_WRITE) { + unsigned int xfer_bytes = data->blksz * data->blocks; - if (!n_elem) { - dev_err(mmc_dev(host->mmc), "dma_map_sg failed\n"); - return -EINVAL; + sg_copy_to_buffer(data->sg, data->sg_len, + idma->bounce_buf, xfer_bytes); + dma_wmb(); + } + } else { + int n_elem; + + n_elem = dma_map_sg(mmc_dev(host->mmc), + data->sg, + data->sg_len, + mmc_get_dma_dir(data)); + + if (!n_elem) { + dev_err(mmc_dev(host->mmc), "dma_map_sg failed\n"); + return -EINVAL; + } } - return 0; } @@ -112,8 +145,19 @@ static int sdmmc_idma_prep_data(struct mmci_host *host, static void sdmmc_idma_unprep_data(struct mmci_host *host, struct mmc_data *data, int err) { - dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, - mmc_get_dma_dir(data)); + struct sdmmc_idma *idma = host->dma_priv; + + if (idma->use_bounce_buffer) { + if (data->flags & MMC_DATA_READ) { + unsigned int xfer_bytes = data->blksz * data->blocks; + + sg_copy_from_buffer(data->sg, data->sg_len, + idma->bounce_buf, xfer_bytes); + } + } else { + dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + mmc_get_dma_dir(data)); + } } static int sdmmc_idma_setup(struct mmci_host *host) @@ -137,6 +181,8 @@ static int sdmmc_idma_setup(struct mmci_host *host) host->mmc->max_segs = SDMMC_LLI_BUF_LEN / sizeof(struct sdmmc_lli_desc); host->mmc->max_seg_size = host->variant->stm32_idmabsize_mask; + + host->mmc->max_req_size = SZ_1M; } else { host->mmc->max_segs = 1; host->mmc->max_seg_size = host->mmc->max_req_size; @@ -154,8 +200,16 @@ static int sdmmc_idma_start(struct mmci_host *host, unsigned int *datactrl) struct scatterlist *sg; int i; - if (!host->variant->dma_lli || data->sg_len == 1) { - writel_relaxed(sg_dma_address(data->sg), + if (!host->variant->dma_lli || data->sg_len == 1 || + idma->use_bounce_buffer) { + u32 dma_addr; + + if (idma->use_bounce_buffer) + dma_addr = idma->bounce_dma_addr; + else + dma_addr = sg_dma_address(data->sg); + + writel_relaxed(dma_addr, host->base + MMCI_STM32_IDMABASE0R); writel_relaxed(MMCI_STM32_IDMAEN, host->base + MMCI_STM32_IDMACTRLR); diff --git a/drivers/mmc/host/mtk-sd.c b/drivers/mmc/host/mtk-sd.c index e61b0b98065a..195dc897188b 100644 --- a/drivers/mmc/host/mtk-sd.c +++ b/drivers/mmc/host/mtk-sd.c @@ -2458,7 +2458,7 @@ static const struct mmc_host_ops mt_msdc_ops = { .execute_tuning = msdc_execute_tuning, .prepare_hs400_tuning = msdc_prepare_hs400_tuning, .execute_hs400_tuning = msdc_execute_hs400_tuning, - .hw_reset = msdc_hw_reset, + .card_hw_reset = msdc_hw_reset, }; static const struct cqhci_host_ops msdc_cmdq_ops = { diff --git a/drivers/mmc/host/of_mmc_spi.c b/drivers/mmc/host/of_mmc_spi.c index 3629550528b6..bf54776fb26c 100644 --- a/drivers/mmc/host/of_mmc_spi.c +++ b/drivers/mmc/host/of_mmc_spi.c @@ -70,6 +70,10 @@ struct mmc_spi_platform_data *mmc_spi_get_pdata(struct spi_device *spi) } else { oms->pdata.caps |= MMC_CAP_NEEDS_POLL; } + if (device_property_read_bool(dev, "cap-sd-highspeed")) + oms->pdata.caps |= MMC_CAP_SD_HIGHSPEED; + if (device_property_read_bool(dev, "cap-mmc-highspeed")) + oms->pdata.caps |= MMC_CAP_MMC_HIGHSPEED; dev->platform_data = &oms->pdata; return dev->platform_data; diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 5e5af34090f1..57d39283924d 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -1374,7 +1374,7 @@ static int mmc_omap_probe(struct platform_device *pdev) host->iclk = clk_get(&pdev->dev, "ick"); if (IS_ERR(host->iclk)) return PTR_ERR(host->iclk); - clk_enable(host->iclk); + clk_prepare_enable(host->iclk); host->fclk = clk_get(&pdev->dev, "fck"); if (IS_ERR(host->fclk)) { @@ -1382,16 +1382,18 @@ static int mmc_omap_probe(struct platform_device *pdev) goto err_free_iclk; } + ret = clk_prepare(host->fclk); + if (ret) + goto err_put_fclk; + host->dma_tx_burst = -1; host->dma_rx_burst = -1; host->dma_tx = dma_request_chan(&pdev->dev, "tx"); if (IS_ERR(host->dma_tx)) { ret = PTR_ERR(host->dma_tx); - if (ret == -EPROBE_DEFER) { - clk_put(host->fclk); - goto err_free_iclk; - } + if (ret == -EPROBE_DEFER) + goto err_free_fclk; host->dma_tx = NULL; dev_warn(host->dev, "TX DMA channel request failed\n"); @@ -1403,8 +1405,7 @@ static int mmc_omap_probe(struct platform_device *pdev) if (ret == -EPROBE_DEFER) { if (host->dma_tx) dma_release_channel(host->dma_tx); - clk_put(host->fclk); - goto err_free_iclk; + goto err_free_fclk; } host->dma_rx = NULL; @@ -1454,9 +1455,12 @@ err_free_dma: dma_release_channel(host->dma_tx); if (host->dma_rx) dma_release_channel(host->dma_rx); +err_free_fclk: + clk_unprepare(host->fclk); +err_put_fclk: clk_put(host->fclk); err_free_iclk: - clk_disable(host->iclk); + clk_disable_unprepare(host->iclk); clk_put(host->iclk); return ret; } @@ -1476,8 +1480,9 @@ static int mmc_omap_remove(struct platform_device *pdev) mmc_omap_fclk_enable(host, 0); free_irq(host->irq, host); + clk_unprepare(host->fclk); clk_put(host->fclk); - clk_disable(host->iclk); + clk_disable_unprepare(host->iclk); clk_put(host->iclk); if (host->dma_tx) diff --git a/drivers/mmc/host/renesas_sdhi.h b/drivers/mmc/host/renesas_sdhi.h index 66d308e73e17..1a1e3e020a8c 100644 --- a/drivers/mmc/host/renesas_sdhi.h +++ b/drivers/mmc/host/renesas_sdhi.h @@ -41,6 +41,8 @@ struct renesas_sdhi_of_data { struct renesas_sdhi_quirks { bool hs400_disabled; bool hs400_4taps; + bool fixed_addr_mode; + bool dma_one_rx_only; u32 hs400_bad_taps; const u8 (*hs400_calib_table)[SDHI_CALIB_TABLE_MAX]; }; diff --git a/drivers/mmc/host/renesas_sdhi_core.c b/drivers/mmc/host/renesas_sdhi_core.c index ddb5ca2f559e..4404ca1f98d8 100644 --- a/drivers/mmc/host/renesas_sdhi_core.c +++ b/drivers/mmc/host/renesas_sdhi_core.c @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -36,7 +35,6 @@ #include #include #include -#include #include "renesas_sdhi.h" #include "tmio_mmc.h" diff --git a/drivers/mmc/host/renesas_sdhi_internal_dmac.c b/drivers/mmc/host/renesas_sdhi_internal_dmac.c index 1685df00863b..3084b15ae2cb 100644 --- a/drivers/mmc/host/renesas_sdhi_internal_dmac.c +++ b/drivers/mmc/host/renesas_sdhi_internal_dmac.c @@ -78,11 +78,7 @@ static unsigned long global_flags; * stored into the system memory even if the DMAC interrupt happened. * So, this driver then uses one RX DMAC channel only. */ -#define SDHI_INTERNAL_DMAC_ONE_RX_ONLY 0 -#define SDHI_INTERNAL_DMAC_RX_IN_USE 1 - -/* RZ/A2 does not have the ADRR_MODE bit */ -#define SDHI_INTERNAL_DMAC_ADDR_MODE_FIXED_ONLY 2 +#define SDHI_INTERNAL_DMAC_RX_IN_USE 0 /* Definitions for sampling clocks */ static struct renesas_sdhi_scc rcar_gen3_scc_taps[] = { @@ -108,10 +104,6 @@ static const struct renesas_sdhi_of_data of_data_rza2 = { .max_segs = 1, }; -static const struct renesas_sdhi_of_data_with_quirks of_rza2_compatible = { - .of_data = &of_data_rza2, -}; - static const struct renesas_sdhi_of_data of_data_rcar_gen3 = { .tmio_flags = TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_CLK_ACTUAL | TMIO_MMC_HAVE_CBSY | TMIO_MMC_MIN_RCAR2, @@ -128,7 +120,7 @@ static const struct renesas_sdhi_of_data of_data_rcar_gen3 = { .sdhi_flags = SDHI_FLAG_NEED_CLKH_FALLBACK, }; -static const struct renesas_sdhi_of_data of_data_rcar_gen3_no_fallback = { +static const struct renesas_sdhi_of_data of_data_rcar_gen3_no_sdh_fallback = { .tmio_flags = TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_CLK_ACTUAL | TMIO_MMC_HAVE_CBSY | TMIO_MMC_MIN_RCAR2, .capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ | @@ -169,6 +161,12 @@ static const struct renesas_sdhi_quirks sdhi_quirks_4tap_nohs400 = { .hs400_4taps = true, }; +static const struct renesas_sdhi_quirks sdhi_quirks_4tap_nohs400_one_rx = { + .hs400_disabled = true, + .hs400_4taps = true, + .dma_one_rx_only = true, +}; + static const struct renesas_sdhi_quirks sdhi_quirks_4tap = { .hs400_4taps = true, .hs400_bad_taps = BIT(2) | BIT(3) | BIT(6) | BIT(7), @@ -178,6 +176,10 @@ static const struct renesas_sdhi_quirks sdhi_quirks_nohs400 = { .hs400_disabled = true, }; +static const struct renesas_sdhi_quirks sdhi_quirks_fixed_addr = { + .fixed_addr_mode = true, +}; + static const struct renesas_sdhi_quirks sdhi_quirks_bad_taps1357 = { .hs400_bad_taps = BIT(1) | BIT(3) | BIT(5) | BIT(7), }; @@ -208,10 +210,12 @@ static const struct renesas_sdhi_quirks sdhi_quirks_r8a77990 = { */ static const struct soc_device_attribute sdhi_quirks_match[] = { { .soc_id = "r8a774a1", .revision = "ES1.[012]", .data = &sdhi_quirks_4tap_nohs400 }, - { .soc_id = "r8a7795", .revision = "ES1.*", .data = &sdhi_quirks_4tap_nohs400 }, + { .soc_id = "r8a7795", .revision = "ES1.*", .data = &sdhi_quirks_4tap_nohs400_one_rx }, { .soc_id = "r8a7795", .revision = "ES2.0", .data = &sdhi_quirks_4tap }, - { .soc_id = "r8a7796", .revision = "ES1.[012]", .data = &sdhi_quirks_4tap_nohs400 }, + { .soc_id = "r8a7796", .revision = "ES1.0", .data = &sdhi_quirks_4tap_nohs400_one_rx }, + { .soc_id = "r8a7796", .revision = "ES1.[12]", .data = &sdhi_quirks_4tap_nohs400 }, { .soc_id = "r8a7796", .revision = "ES1.*", .data = &sdhi_quirks_r8a7796_es13 }, + { .soc_id = "r8a77980", .revision = "ES1.*", .data = &sdhi_quirks_nohs400 }, { /* Sentinel. */ } }; @@ -231,11 +235,7 @@ static const struct renesas_sdhi_of_data_with_quirks of_r8a77965_compatible = { }; static const struct renesas_sdhi_of_data_with_quirks of_r8a77970_compatible = { - .of_data = &of_data_rcar_gen3_no_fallback, -}; - -static const struct renesas_sdhi_of_data_with_quirks of_r8a77980_compatible = { - .of_data = &of_data_rcar_gen3, + .of_data = &of_data_rcar_gen3_no_sdh_fallback, .quirks = &sdhi_quirks_nohs400, }; @@ -248,16 +248,25 @@ static const struct renesas_sdhi_of_data_with_quirks of_rcar_gen3_compatible = { .of_data = &of_data_rcar_gen3, }; +static const struct renesas_sdhi_of_data_with_quirks of_rcar_gen3_nohs400_compatible = { + .of_data = &of_data_rcar_gen3, + .quirks = &sdhi_quirks_nohs400, +}; + +static const struct renesas_sdhi_of_data_with_quirks of_rza2_compatible = { + .of_data = &of_data_rza2, + .quirks = &sdhi_quirks_fixed_addr, +}; + static const struct of_device_id renesas_sdhi_internal_dmac_of_match[] = { { .compatible = "renesas,sdhi-r7s9210", .data = &of_rza2_compatible, }, { .compatible = "renesas,sdhi-mmc-r8a77470", .data = &of_rcar_gen3_compatible, }, { .compatible = "renesas,sdhi-r8a7795", .data = &of_r8a7795_compatible, }, - { .compatible = "renesas,sdhi-r8a7796", .data = &of_rcar_gen3_compatible, }, { .compatible = "renesas,sdhi-r8a77961", .data = &of_r8a77961_compatible, }, { .compatible = "renesas,sdhi-r8a77965", .data = &of_r8a77965_compatible, }, { .compatible = "renesas,sdhi-r8a77970", .data = &of_r8a77970_compatible, }, - { .compatible = "renesas,sdhi-r8a77980", .data = &of_r8a77980_compatible, }, { .compatible = "renesas,sdhi-r8a77990", .data = &of_r8a77990_compatible, }, + { .compatible = "renesas,sdhi-r8a77995", .data = &of_rcar_gen3_nohs400_compatible, }, { .compatible = "renesas,rcar-gen3-sdhi", .data = &of_rcar_gen3_compatible, }, {}, }; @@ -287,7 +296,8 @@ renesas_sdhi_internal_dmac_enable_dma(struct tmio_mmc_host *host, bool enable) } static void -renesas_sdhi_internal_dmac_abort_dma(struct tmio_mmc_host *host) { +renesas_sdhi_internal_dmac_abort_dma(struct tmio_mmc_host *host) +{ u64 val = RST_DTRANRST1 | RST_DTRANRST0; renesas_sdhi_internal_dmac_enable_dma(host, false); @@ -303,7 +313,8 @@ renesas_sdhi_internal_dmac_abort_dma(struct tmio_mmc_host *host) { } static void -renesas_sdhi_internal_dmac_dataend_dma(struct tmio_mmc_host *host) { +renesas_sdhi_internal_dmac_dataend_dma(struct tmio_mmc_host *host) +{ struct renesas_sdhi *priv = host_to_priv(host); tasklet_schedule(&priv->dma_priv.dma_complete); @@ -357,10 +368,11 @@ static void renesas_sdhi_internal_dmac_start_dma(struct tmio_mmc_host *host, struct mmc_data *data) { + struct renesas_sdhi *priv = host_to_priv(host); struct scatterlist *sg = host->sg_ptr; u32 dtran_mode = DTRAN_MODE_BUS_WIDTH; - if (!test_bit(SDHI_INTERNAL_DMAC_ADDR_MODE_FIXED_ONLY, &global_flags)) + if (!(priv->quirks && priv->quirks->fixed_addr_mode)) dtran_mode |= DTRAN_MODE_ADDR_MODE; if (!renesas_sdhi_internal_dmac_map(host, data, COOKIE_MAPPED)) @@ -368,7 +380,7 @@ renesas_sdhi_internal_dmac_start_dma(struct tmio_mmc_host *host, if (data->flags & MMC_DATA_READ) { dtran_mode |= DTRAN_MODE_CH_NUM_CH1; - if (test_bit(SDHI_INTERNAL_DMAC_ONE_RX_ONLY, &global_flags) && + if (priv->quirks && priv->quirks->dma_one_rx_only && test_and_set_bit(SDHI_INTERNAL_DMAC_RX_IN_USE, &global_flags)) goto force_pio_with_unmap; } else { @@ -520,20 +532,6 @@ static const struct tmio_mmc_dma_ops renesas_sdhi_internal_dmac_dma_ops = { .end = renesas_sdhi_internal_dmac_end_dma, }; -/* - * Whitelist of specific R-Car Gen3 SoC ES versions to use this DMAC - * implementation as others may use a different implementation. - */ -static const struct soc_device_attribute soc_dma_quirks[] = { - { .soc_id = "r7s9210", - .data = (void *)BIT(SDHI_INTERNAL_DMAC_ADDR_MODE_FIXED_ONLY) }, - { .soc_id = "r8a7795", .revision = "ES1.*", - .data = (void *)BIT(SDHI_INTERNAL_DMAC_ONE_RX_ONLY) }, - { .soc_id = "r8a7796", .revision = "ES1.0", - .data = (void *)BIT(SDHI_INTERNAL_DMAC_ONE_RX_ONLY) }, - { /* sentinel */ } -}; - static int renesas_sdhi_internal_dmac_probe(struct platform_device *pdev) { const struct soc_device_attribute *attr; @@ -544,10 +542,6 @@ static int renesas_sdhi_internal_dmac_probe(struct platform_device *pdev) of_data_quirks = of_device_get_match_data(&pdev->dev); quirks = of_data_quirks->quirks; - attr = soc_device_match(soc_dma_quirks); - if (attr) - global_flags |= (unsigned long)attr->data; - attr = soc_device_match(sdhi_quirks_match); if (attr) quirks = attr->data; diff --git a/drivers/mmc/host/sdhci-brcmstb.c b/drivers/mmc/host/sdhci-brcmstb.c index f24623aac2db..8eb57de48e0c 100644 --- a/drivers/mmc/host/sdhci-brcmstb.c +++ b/drivers/mmc/host/sdhci-brcmstb.c @@ -17,23 +17,49 @@ #define SDHCI_VENDOR 0x78 #define SDHCI_VENDOR_ENHANCED_STRB 0x1 +#define SDHCI_VENDOR_GATE_SDCLK_EN 0x2 -#define BRCMSTB_PRIV_FLAGS_NO_64BIT BIT(0) -#define BRCMSTB_PRIV_FLAGS_BROKEN_TIMEOUT BIT(1) +#define BRCMSTB_MATCH_FLAGS_NO_64BIT BIT(0) +#define BRCMSTB_MATCH_FLAGS_BROKEN_TIMEOUT BIT(1) +#define BRCMSTB_MATCH_FLAGS_HAS_CLOCK_GATE BIT(2) + +#define BRCMSTB_PRIV_FLAGS_HAS_CQE BIT(0) +#define BRCMSTB_PRIV_FLAGS_GATE_CLOCK BIT(1) #define SDHCI_ARASAN_CQE_BASE_ADDR 0x200 struct sdhci_brcmstb_priv { void __iomem *cfg_regs; - bool has_cqe; + unsigned int flags; }; struct brcmstb_match_priv { void (*hs400es)(struct mmc_host *mmc, struct mmc_ios *ios); struct sdhci_ops *ops; - unsigned int flags; + const unsigned int flags; }; +static inline void enable_clock_gating(struct sdhci_host *host) +{ + u32 reg; + + reg = sdhci_readl(host, SDHCI_VENDOR); + reg |= SDHCI_VENDOR_GATE_SDCLK_EN; + sdhci_writel(host, reg, SDHCI_VENDOR); +} + +static void brcmstb_reset(struct sdhci_host *host, u8 mask) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(pltfm_host); + + sdhci_reset(host, mask); + + /* Reset will clear this, so re-enable it */ + if (priv->flags & BRCMSTB_PRIV_FLAGS_GATE_CLOCK) + enable_clock_gating(host); +} + static void sdhci_brcmstb_hs400es(struct mmc_host *mmc, struct mmc_ios *ios) { struct sdhci_host *host = mmc_priv(mmc); @@ -129,22 +155,23 @@ static struct sdhci_ops sdhci_brcmstb_ops = { static struct sdhci_ops sdhci_brcmstb_ops_7216 = { .set_clock = sdhci_brcmstb_set_clock, .set_bus_width = sdhci_set_bus_width, - .reset = sdhci_reset, + .reset = brcmstb_reset, .set_uhs_signaling = sdhci_brcmstb_set_uhs_signaling, }; static struct brcmstb_match_priv match_priv_7425 = { - .flags = BRCMSTB_PRIV_FLAGS_NO_64BIT | - BRCMSTB_PRIV_FLAGS_BROKEN_TIMEOUT, + .flags = BRCMSTB_MATCH_FLAGS_NO_64BIT | + BRCMSTB_MATCH_FLAGS_BROKEN_TIMEOUT, .ops = &sdhci_brcmstb_ops, }; static struct brcmstb_match_priv match_priv_7445 = { - .flags = BRCMSTB_PRIV_FLAGS_BROKEN_TIMEOUT, + .flags = BRCMSTB_MATCH_FLAGS_BROKEN_TIMEOUT, .ops = &sdhci_brcmstb_ops, }; static const struct brcmstb_match_priv match_priv_7216 = { + .flags = BRCMSTB_MATCH_FLAGS_HAS_CLOCK_GATE, .hs400es = sdhci_brcmstb_hs400es, .ops = &sdhci_brcmstb_ops_7216, }; @@ -176,7 +203,7 @@ static int sdhci_brcmstb_add_host(struct sdhci_host *host, bool dma64; int ret; - if (!priv->has_cqe) + if ((priv->flags & BRCMSTB_PRIV_FLAGS_HAS_CQE) == 0) return sdhci_add_host(host); dev_dbg(mmc_dev(host->mmc), "CQE is enabled\n"); @@ -225,7 +252,6 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) struct sdhci_brcmstb_priv *priv; struct sdhci_host *host; struct resource *iomem; - bool has_cqe = false; struct clk *clk; int res; @@ -244,10 +270,6 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) return res; memset(&brcmstb_pdata, 0, sizeof(brcmstb_pdata)); - if (device_property_read_bool(&pdev->dev, "supports-cqe")) { - has_cqe = true; - match_priv->ops->irq = sdhci_brcmstb_cqhci_irq; - } brcmstb_pdata.ops = match_priv->ops; host = sdhci_pltfm_init(pdev, &brcmstb_pdata, sizeof(struct sdhci_brcmstb_priv)); @@ -258,7 +280,10 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) pltfm_host = sdhci_priv(host); priv = sdhci_pltfm_priv(pltfm_host); - priv->has_cqe = has_cqe; + if (device_property_read_bool(&pdev->dev, "supports-cqe")) { + priv->flags |= BRCMSTB_PRIV_FLAGS_HAS_CQE; + match_priv->ops->irq = sdhci_brcmstb_cqhci_irq; + } /* Map in the non-standard CFG registers */ iomem = platform_get_resource(pdev, IORESOURCE_MEM, 1); @@ -273,6 +298,14 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) if (res) goto err; + /* + * Automatic clock gating does not work for SD cards that may + * voltage switch so only enable it for non-removable devices. + */ + if ((match_priv->flags & BRCMSTB_MATCH_FLAGS_HAS_CLOCK_GATE) && + (host->mmc->caps & MMC_CAP_NONREMOVABLE)) + priv->flags |= BRCMSTB_PRIV_FLAGS_GATE_CLOCK; + /* * If the chip has enhanced strobe and it's enabled, add * callback @@ -287,14 +320,14 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) * properties through mmc_of_parse(). */ host->caps = sdhci_readl(host, SDHCI_CAPABILITIES); - if (match_priv->flags & BRCMSTB_PRIV_FLAGS_NO_64BIT) + if (match_priv->flags & BRCMSTB_MATCH_FLAGS_NO_64BIT) host->caps &= ~SDHCI_CAN_64BIT; host->caps1 = sdhci_readl(host, SDHCI_CAPABILITIES_1); host->caps1 &= ~(SDHCI_SUPPORT_SDR50 | SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_DDR50); host->quirks |= SDHCI_QUIRK_MISSING_CAPS; - if (match_priv->flags & BRCMSTB_PRIV_FLAGS_BROKEN_TIMEOUT) + if (match_priv->flags & BRCMSTB_MATCH_FLAGS_BROKEN_TIMEOUT) host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL; res = sdhci_brcmstb_add_host(host, priv); diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index ff9f5b63c337..e395411fb6fd 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2435,8 +2435,33 @@ static const struct sdhci_msm_variant_info sdm845_sdhci_var = { }; static const struct of_device_id sdhci_msm_dt_match[] = { + /* Following two entries are deprecated (kept only for backward compatibility) */ {.compatible = "qcom,sdhci-msm-v4", .data = &sdhci_msm_mci_var}, {.compatible = "qcom,sdhci-msm-v5", .data = &sdhci_msm_v5_var}, + /* Add entries for sdcc versions less than 5.0 here */ + {.compatible = "qcom,apq8084-sdhci", .data = &sdhci_msm_mci_var}, + {.compatible = "qcom,msm8226-sdhci", .data = &sdhci_msm_mci_var}, + {.compatible = "qcom,msm8916-sdhci", .data = &sdhci_msm_mci_var}, + {.compatible = "qcom,msm8953-sdhci", .data = &sdhci_msm_mci_var}, + {.compatible = "qcom,msm8974-sdhci", .data = &sdhci_msm_mci_var}, + {.compatible = "qcom,msm8992-sdhci", .data = &sdhci_msm_mci_var}, + {.compatible = "qcom,msm8994-sdhci", .data = &sdhci_msm_mci_var}, + {.compatible = "qcom,msm8996-sdhci", .data = &sdhci_msm_mci_var}, + /* + * Add entries for sdcc version 5.0 here. For SDCC version 5.0.0, + * MCI registers are removed from SDCC interface and some registers + * are moved to HC. + */ + {.compatible = "qcom,qcs404-sdhci", .data = &sdhci_msm_v5_var}, + {.compatible = "qcom,sdx55-sdhci", .data = &sdhci_msm_v5_var}, + {.compatible = "qcom,sdx65-sdhci", .data = &sdhci_msm_v5_var}, + {.compatible = "qcom,sdm630-sdhci", .data = &sdhci_msm_v5_var}, + {.compatible = "qcom,sm6125-sdhci", .data = &sdhci_msm_v5_var}, + {.compatible = "qcom,sm6350-sdhci", .data = &sdhci_msm_v5_var}, + {.compatible = "qcom,sm8150-sdhci", .data = &sdhci_msm_v5_var}, + {.compatible = "qcom,sm8250-sdhci", .data = &sdhci_msm_v5_var}, + {.compatible = "qcom,sc7280-sdhci", .data = &sdhci_msm_v5_var}, + /* Add entries where soc specific handling is required, here */ {.compatible = "qcom,sdm845-sdhci", .data = &sdm845_sdhci_var}, {.compatible = "qcom,sc7180-sdhci", .data = &sdm845_sdhci_var}, {}, diff --git a/drivers/mmc/host/sdhci-of-arasan.c b/drivers/mmc/host/sdhci-of-arasan.c index 6a2e5a468424..757801dfc308 100644 --- a/drivers/mmc/host/sdhci-of-arasan.c +++ b/drivers/mmc/host/sdhci-of-arasan.c @@ -1577,6 +1577,9 @@ static int sdhci_arasan_probe(struct platform_device *pdev) const struct sdhci_arasan_of_data *data; data = of_device_get_match_data(dev); + if (!data) + return -EINVAL; + host = sdhci_pltfm_init(pdev, data->pdata, sizeof(*sdhci_arasan)); if (IS_ERR(host)) diff --git a/drivers/mmc/host/sdhci-omap.c b/drivers/mmc/host/sdhci-omap.c index 64e27c2821f9..86e867ffbb10 100644 --- a/drivers/mmc/host/sdhci-omap.c +++ b/drivers/mmc/host/sdhci-omap.c @@ -1219,16 +1219,11 @@ static int sdhci_omap_probe(struct platform_device *pdev) struct sdhci_pltfm_host *pltfm_host; struct sdhci_omap_host *omap_host; struct mmc_host *mmc; - const struct of_device_id *match; - struct sdhci_omap_data *data; + const struct sdhci_omap_data *data; const struct soc_device_attribute *soc; struct resource *regs; - match = of_match_device(omap_sdhci_match, dev); - if (!match) - return -EINVAL; - - data = (struct sdhci_omap_data *)match->data; + data = of_device_get_match_data(&pdev->dev); if (!data) { dev_err(dev, "no sdhci omap data\n"); return -EINVAL; diff --git a/drivers/mmc/host/sdhci-pci-gli.c b/drivers/mmc/host/sdhci-pci-gli.c index d09728c37d03..1499a64ec3aa 100644 --- a/drivers/mmc/host/sdhci-pci-gli.c +++ b/drivers/mmc/host/sdhci-pci-gli.c @@ -142,6 +142,9 @@ #define PCI_GLI_9755_MISC 0x78 #define PCI_GLI_9755_MISC_SSC_OFF BIT(26) +#define PCI_GLI_9755_PM_CTRL 0xFC +#define PCI_GLI_9755_PM_STATE GENMASK(1, 0) + #define GLI_MAX_TUNING_LOOP 40 /* Genesys Logic chipset */ @@ -676,6 +679,13 @@ static void gl9755_hw_setting(struct sdhci_pci_slot *slot) GLI_9755_CFG2_L1DLY_VALUE); pci_write_config_dword(pdev, PCI_GLI_9755_CFG2, value); + /* toggle PM state to allow GL9755 to enter ASPM L1.2 */ + pci_read_config_dword(pdev, PCI_GLI_9755_PM_CTRL, &value); + value |= PCI_GLI_9755_PM_STATE; + pci_write_config_dword(pdev, PCI_GLI_9755_PM_CTRL, value); + value &= ~PCI_GLI_9755_PM_STATE; + pci_write_config_dword(pdev, PCI_GLI_9755_PM_CTRL, value); + gl9755_wt_off(pdev); } diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 07c6da1f2f0f..22152029e14c 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2999,7 +2999,7 @@ static const struct mmc_host_ops sdhci_ops = { .set_ios = sdhci_set_ios, .get_cd = sdhci_get_cd, .get_ro = sdhci_get_ro, - .hw_reset = sdhci_hw_reset, + .card_hw_reset = sdhci_hw_reset, .enable_sdio_irq = sdhci_enable_sdio_irq, .ack_sdio_irq = sdhci_ack_sdio_irq, .start_signal_voltage_switch = sdhci_start_signal_voltage_switch, diff --git a/drivers/mmc/host/sdhci_am654.c b/drivers/mmc/host/sdhci_am654.c index e54fe24d47e7..e7ced1496a07 100644 --- a/drivers/mmc/host/sdhci_am654.c +++ b/drivers/mmc/host/sdhci_am654.c @@ -147,6 +147,9 @@ struct sdhci_am654_data { int drv_strength; int strb_sel; u32 flags; + u32 quirks; + +#define SDHCI_AM654_QUIRK_FORCE_CDTEST BIT(0) }; struct sdhci_am654_driver_data { @@ -369,6 +372,21 @@ static void sdhci_am654_write_b(struct sdhci_host *host, u8 val, int reg) } } +static void sdhci_am654_reset(struct sdhci_host *host, u8 mask) +{ + u8 ctrl; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_am654_data *sdhci_am654 = sdhci_pltfm_priv(pltfm_host); + + sdhci_reset(host, mask); + + if (sdhci_am654->quirks & SDHCI_AM654_QUIRK_FORCE_CDTEST) { + ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); + ctrl |= SDHCI_CTRL_CDTEST_INS | SDHCI_CTRL_CDTEST_EN; + sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); + } +} + static int sdhci_am654_execute_tuning(struct mmc_host *mmc, u32 opcode) { struct sdhci_host *host = mmc_priv(mmc); @@ -500,7 +518,7 @@ static struct sdhci_ops sdhci_j721e_4bit_ops = { .set_clock = sdhci_j721e_4bit_set_clock, .write_b = sdhci_am654_write_b, .irq = sdhci_am654_cqhci_irq, - .reset = sdhci_reset, + .reset = sdhci_am654_reset, }; static const struct sdhci_pltfm_data sdhci_j721e_4bit_pdata = { @@ -719,6 +737,9 @@ static int sdhci_am654_get_of_property(struct platform_device *pdev, device_property_read_u32(dev, "ti,clkbuf-sel", &sdhci_am654->clkbuf_sel); + if (device_property_read_bool(dev, "ti,fails-without-test-cd")) + sdhci_am654->quirks |= SDHCI_AM654_QUIRK_FORCE_CDTEST; + sdhci_get_of_property(pdev); return 0; diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 5f9ebf045b1c..0fd4c9d644dd 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -43,12 +43,12 @@ #include #include #include -#include #include #include #include #include #include +#include #include #include #include diff --git a/drivers/mmc/host/sunxi-mmc.c b/drivers/mmc/host/sunxi-mmc.c index 46f9e2923d86..b16e12e62e72 100644 --- a/drivers/mmc/host/sunxi-mmc.c +++ b/drivers/mmc/host/sunxi-mmc.c @@ -1116,7 +1116,7 @@ static const struct mmc_host_ops sunxi_mmc_ops = { .get_cd = mmc_gpio_get_cd, .enable_sdio_irq = sunxi_mmc_enable_sdio_irq, .start_signal_voltage_switch = sunxi_mmc_volt_switch, - .hw_reset = sunxi_mmc_hw_reset, + .card_hw_reset = sunxi_mmc_hw_reset, .card_busy = sunxi_mmc_card_busy, }; diff --git a/drivers/mmc/host/uniphier-sd.c b/drivers/mmc/host/uniphier-sd.c index ccbf9885a52b..3a8defdcca77 100644 --- a/drivers/mmc/host/uniphier-sd.c +++ b/drivers/mmc/host/uniphier-sd.c @@ -597,7 +597,7 @@ static int uniphier_sd_probe(struct platform_device *pdev) ret = PTR_ERR(priv->rst_hw); goto free_host; } - host->ops.hw_reset = uniphier_sd_hw_reset; + host->ops.card_hw_reset = uniphier_sd_hw_reset; } if (host->mmc->caps & MMC_CAP_UHS) { diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c index a761134fd3be..67453f59c69c 100644 --- a/drivers/mtd/chips/cfi_cmdset_0002.c +++ b/drivers/mtd/chips/cfi_cmdset_0002.c @@ -48,6 +48,7 @@ #define SST49LF040B 0x0050 #define SST49LF008A 0x005a #define AT49BV6416 0x00d6 +#define S29GL064N_MN12 0x0c01 /* * Status Register bit description. Used by flash devices that don't @@ -59,6 +60,10 @@ #define CFI_SR_WBASB BIT(3) #define CFI_SR_SLSB BIT(1) +enum cfi_quirks { + CFI_QUIRK_DQ_TRUE_DATA = BIT(0), +}; + static int cfi_amdstd_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *); static int cfi_amdstd_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *); #if !FORCE_WORD_WRITE @@ -436,6 +441,15 @@ static void fixup_s29ns512p_sectors(struct mtd_info *mtd) mtd->name); } +static void fixup_quirks(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + if (cfi->mfr == CFI_MFR_AMD && cfi->id == S29GL064N_MN12) + cfi->quirks |= CFI_QUIRK_DQ_TRUE_DATA; +} + /* Used to fix CFI-Tables of chips without Extended Query Tables */ static struct cfi_fixup cfi_nopri_fixup_table[] = { { CFI_MFR_SST, 0x234a, fixup_sst39vf }, /* SST39VF1602 */ @@ -462,7 +476,7 @@ static struct cfi_fixup cfi_fixup_table[] = { { CFI_MFR_AMD, 0x0056, fixup_use_secsi }, { CFI_MFR_AMD, 0x005C, fixup_use_secsi }, { CFI_MFR_AMD, 0x005F, fixup_use_secsi }, - { CFI_MFR_AMD, 0x0c01, fixup_s29gl064n_sectors }, + { CFI_MFR_AMD, S29GL064N_MN12, fixup_s29gl064n_sectors }, { CFI_MFR_AMD, 0x1301, fixup_s29gl064n_sectors }, { CFI_MFR_AMD, 0x1a00, fixup_s29gl032n_sectors }, { CFI_MFR_AMD, 0x1a01, fixup_s29gl032n_sectors }, @@ -474,6 +488,7 @@ static struct cfi_fixup cfi_fixup_table[] = { #if !FORCE_WORD_WRITE { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers }, #endif + { CFI_MFR_ANY, CFI_ID_ANY, fixup_quirks }, { 0, 0, NULL } }; static struct cfi_fixup jedec_fixup_table[] = { @@ -801,47 +816,11 @@ static struct mtd_info *cfi_amdstd_setup(struct mtd_info *mtd) return NULL; } -/* - * Return true if the chip is ready. - * - * Ready is one of: read mode, query mode, erase-suspend-read mode (in any - * non-suspended sector) and is indicated by no toggle bits toggling. - * - * Note that anything more complicated than checking if no bits are toggling - * (including checking DQ5 for an error status) is tricky to get working - * correctly and is therefore not done (particularly with interleaved chips - * as each chip must be checked independently of the others). - */ -static int __xipram chip_ready(struct map_info *map, struct flchip *chip, - unsigned long addr) -{ - struct cfi_private *cfi = map->fldrv_priv; - map_word d, t; - - if (cfi_use_status_reg(cfi)) { - map_word ready = CMD(CFI_SR_DRB); - /* - * For chips that support status register, check device - * ready bit - */ - cfi_send_gen_cmd(0x70, cfi->addr_unlock1, chip->start, map, cfi, - cfi->device_type, NULL); - d = map_read(map, addr); - - return map_word_andequal(map, d, ready, ready); - } - - d = map_read(map, addr); - t = map_read(map, addr); - - return map_word_equal(map, d, t); -} - /* * Return true if the chip is ready and has the correct value. * * Ready is one of: read mode, query mode, erase-suspend-read mode (in any - * non-suspended sector) and it is indicated by no bits toggling. + * non-suspended sector) and is indicated by no toggle bits toggling. * * Error are indicated by toggling bits or bits held with the wrong value, * or with bits toggling. @@ -850,17 +829,16 @@ static int __xipram chip_ready(struct map_info *map, struct flchip *chip, * (including checking DQ5 for an error status) is tricky to get working * correctly and is therefore not done (particularly with interleaved chips * as each chip must be checked independently of the others). - * */ -static int __xipram chip_good(struct map_info *map, struct flchip *chip, - unsigned long addr, map_word expected) +static int __xipram chip_ready(struct map_info *map, struct flchip *chip, + unsigned long addr, map_word *expected) { struct cfi_private *cfi = map->fldrv_priv; map_word oldd, curd; + int ret; if (cfi_use_status_reg(cfi)) { map_word ready = CMD(CFI_SR_DRB); - /* * For chips that support status register, check device * ready bit @@ -875,8 +853,24 @@ static int __xipram chip_good(struct map_info *map, struct flchip *chip, oldd = map_read(map, addr); curd = map_read(map, addr); - return map_word_equal(map, oldd, curd) && - map_word_equal(map, curd, expected); + ret = map_word_equal(map, oldd, curd); + + if (!ret || !expected) + return ret; + + return map_word_equal(map, curd, *expected); +} + +static int __xipram chip_good(struct map_info *map, struct flchip *chip, + unsigned long addr, map_word *expected) +{ + struct cfi_private *cfi = map->fldrv_priv; + map_word *datum = expected; + + if (cfi->quirks & CFI_QUIRK_DQ_TRUE_DATA) + datum = NULL; + + return chip_ready(map, chip, addr, datum); } static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode) @@ -893,7 +887,7 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr case FL_STATUS: for (;;) { - if (chip_ready(map, chip, adr)) + if (chip_ready(map, chip, adr, NULL)) break; if (time_after(jiffies, timeo)) { @@ -932,7 +926,7 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr chip->state = FL_ERASE_SUSPENDING; chip->erase_suspended = 1; for (;;) { - if (chip_ready(map, chip, adr)) + if (chip_ready(map, chip, adr, NULL)) break; if (time_after(jiffies, timeo)) { @@ -1463,7 +1457,7 @@ static int do_otp_lock(struct map_info *map, struct flchip *chip, loff_t adr, /* wait for chip to become ready */ timeo = jiffies + msecs_to_jiffies(2); for (;;) { - if (chip_ready(map, chip, adr)) + if (chip_ready(map, chip, adr, NULL)) break; if (time_after(jiffies, timeo)) { @@ -1699,7 +1693,7 @@ static int __xipram do_write_oneword_once(struct map_info *map, * "chip_good" to avoid the failure due to scheduling. */ if (time_after(jiffies, timeo) && - !chip_good(map, chip, adr, datum)) { + !chip_good(map, chip, adr, &datum)) { xip_enable(map, chip, adr); printk(KERN_WARNING "MTD %s(): software timeout\n", __func__); xip_disable(map, chip, adr); @@ -1707,7 +1701,7 @@ static int __xipram do_write_oneword_once(struct map_info *map, break; } - if (chip_good(map, chip, adr, datum)) { + if (chip_good(map, chip, adr, &datum)) { if (cfi_check_err_status(map, chip, adr)) ret = -EIO; break; @@ -1979,14 +1973,14 @@ static int __xipram do_write_buffer_wait(struct map_info *map, * "chip_good" to avoid the failure due to scheduling. */ if (time_after(jiffies, timeo) && - !chip_good(map, chip, adr, datum)) { + !chip_good(map, chip, adr, &datum)) { pr_err("MTD %s(): software timeout, address:0x%.8lx.\n", __func__, adr); ret = -EIO; break; } - if (chip_good(map, chip, adr, datum)) { + if (chip_good(map, chip, adr, &datum)) { if (cfi_check_err_status(map, chip, adr)) ret = -EIO; break; @@ -2195,7 +2189,7 @@ static int cfi_amdstd_panic_wait(struct map_info *map, struct flchip *chip, * If the driver thinks the chip is idle, and no toggle bits * are changing, then the chip is actually idle for sure. */ - if (chip->state == FL_READY && chip_ready(map, chip, adr)) + if (chip->state == FL_READY && chip_ready(map, chip, adr, NULL)) return 0; /* @@ -2212,7 +2206,7 @@ static int cfi_amdstd_panic_wait(struct map_info *map, struct flchip *chip, /* wait for the chip to become ready */ for (i = 0; i < jiffies_to_usecs(timeo); i++) { - if (chip_ready(map, chip, adr)) + if (chip_ready(map, chip, adr, NULL)) return 0; udelay(1); @@ -2276,13 +2270,13 @@ retry: map_write(map, datum, adr); for (i = 0; i < jiffies_to_usecs(uWriteTimeout); i++) { - if (chip_ready(map, chip, adr)) + if (chip_ready(map, chip, adr, NULL)) break; udelay(1); } - if (!chip_good(map, chip, adr, datum) || + if (!chip_ready(map, chip, adr, &datum) || cfi_check_err_status(map, chip, adr)) { /* reset on all failures. */ map_write(map, CMD(0xF0), chip->start); @@ -2424,6 +2418,7 @@ static int __xipram do_erase_chip(struct map_info *map, struct flchip *chip) DECLARE_WAITQUEUE(wait, current); int ret; int retry_cnt = 0; + map_word datum = map_word_ff(map); adr = cfi->addr_unlock1; @@ -2478,7 +2473,7 @@ static int __xipram do_erase_chip(struct map_info *map, struct flchip *chip) chip->erase_suspended = 0; } - if (chip_good(map, chip, adr, map_word_ff(map))) { + if (chip_ready(map, chip, adr, &datum)) { if (cfi_check_err_status(map, chip, adr)) ret = -EIO; break; @@ -2523,6 +2518,7 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip, DECLARE_WAITQUEUE(wait, current); int ret; int retry_cnt = 0; + map_word datum = map_word_ff(map); adr += chip->start; @@ -2577,7 +2573,7 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip, chip->erase_suspended = 0; } - if (chip_good(map, chip, adr, map_word_ff(map))) { + if (chip_ready(map, chip, adr, &datum)) { if (cfi_check_err_status(map, chip, adr)) ret = -EIO; break; @@ -2771,7 +2767,7 @@ static int __maybe_unused do_ppb_xxlock(struct map_info *map, */ timeo = jiffies + msecs_to_jiffies(2000); /* 2s max (un)locking */ for (;;) { - if (chip_ready(map, chip, adr)) + if (chip_ready(map, chip, adr, NULL)) break; if (time_after(jiffies, timeo)) { diff --git a/drivers/mtd/devices/phram.c b/drivers/mtd/devices/phram.c index d503821a3e60..208bd4d871f4 100644 --- a/drivers/mtd/devices/phram.c +++ b/drivers/mtd/devices/phram.c @@ -27,10 +27,14 @@ #include #include #include +#include +#include +#include struct phram_mtd_list { struct mtd_info mtd; struct list_head list; + bool cached; }; static LIST_HEAD(phram_list); @@ -77,20 +81,51 @@ static int phram_write(struct mtd_info *mtd, loff_t to, size_t len, return 0; } +static int phram_map(struct phram_mtd_list *phram, phys_addr_t start, size_t len) +{ + void *addr = NULL; + + if (phram->cached) + addr = memremap(start, len, MEMREMAP_WB); + else + addr = (void __force *)ioremap(start, len); + if (!addr) + return -EIO; + + phram->mtd.priv = addr; + + return 0; +} + +static void phram_unmap(struct phram_mtd_list *phram) +{ + void *addr = phram->mtd.priv; + + if (phram->cached) { + memunmap(addr); + return; + } + + iounmap((void __iomem *)addr); +} + static void unregister_devices(void) { struct phram_mtd_list *this, *safe; list_for_each_entry_safe(this, safe, &phram_list, list) { mtd_device_unregister(&this->mtd); - iounmap(this->mtd.priv); + phram_unmap(this); kfree(this->mtd.name); kfree(this); } } -static int register_device(char *name, phys_addr_t start, size_t len, uint32_t erasesize) +static int register_device(struct platform_device *pdev, const char *name, + phys_addr_t start, size_t len, uint32_t erasesize) { + struct device_node *np = pdev ? pdev->dev.of_node : NULL; + bool cached = np ? !of_property_read_bool(np, "no-map") : false; struct phram_mtd_list *new; int ret = -ENOMEM; @@ -98,9 +133,10 @@ static int register_device(char *name, phys_addr_t start, size_t len, uint32_t e if (!new) goto out0; - ret = -EIO; - new->mtd.priv = ioremap(start, len); - if (!new->mtd.priv) { + new->cached = cached; + + ret = phram_map(new, start, len); + if (ret) { pr_err("ioremap failed\n"); goto out1; } @@ -119,17 +155,23 @@ static int register_device(char *name, phys_addr_t start, size_t len, uint32_t e new->mtd.erasesize = erasesize; new->mtd.writesize = 1; + mtd_set_of_node(&new->mtd, np); + ret = -EAGAIN; if (mtd_device_register(&new->mtd, NULL, 0)) { pr_err("Failed to register new device\n"); goto out2; } - list_add_tail(&new->list, &phram_list); + if (pdev) + platform_set_drvdata(pdev, new); + else + list_add_tail(&new->list, &phram_list); + return 0; out2: - iounmap(new->mtd.priv); + phram_unmap(new); out1: kfree(new); out0: @@ -278,7 +320,7 @@ static int phram_setup(const char *val) goto error; } - ret = register_device(name, start, len, (uint32_t)erasesize); + ret = register_device(NULL, name, start, len, (uint32_t)erasesize); if (ret) goto error; @@ -325,10 +367,54 @@ static int phram_param_call(const char *val, const struct kernel_param *kp) module_param_call(phram, phram_param_call, NULL, NULL, 0200); MODULE_PARM_DESC(phram, "Memory region to map. \"phram=,,[,]\""); +#ifdef CONFIG_OF +static const struct of_device_id phram_of_match[] = { + { .compatible = "phram" }, + {} +}; +MODULE_DEVICE_TABLE(of, phram_of_match); +#endif + +static int phram_probe(struct platform_device *pdev) +{ + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOMEM; + + /* mtd_set_of_node() reads name from "label" */ + return register_device(pdev, NULL, res->start, resource_size(res), + PAGE_SIZE); +} + +static int phram_remove(struct platform_device *pdev) +{ + struct phram_mtd_list *phram = platform_get_drvdata(pdev); + + mtd_device_unregister(&phram->mtd); + phram_unmap(phram); + kfree(phram); + + return 0; +} + +static struct platform_driver phram_driver = { + .probe = phram_probe, + .remove = phram_remove, + .driver = { + .name = "phram", + .of_match_table = of_match_ptr(phram_of_match), + }, +}; static int __init init_phram(void) { - int ret = 0; + int ret; + + ret = platform_driver_register(&phram_driver); + if (ret) + return ret; #ifndef MODULE if (phram_paramline[0]) @@ -336,12 +422,16 @@ static int __init init_phram(void) phram_init_called = 1; #endif + if (ret) + platform_driver_unregister(&phram_driver); + return ret; } static void __exit cleanup_phram(void) { unregister_devices(); + platform_driver_unregister(&phram_driver); } module_init(init_phram); diff --git a/drivers/mtd/devices/st_spi_fsm.c b/drivers/mtd/devices/st_spi_fsm.c index 983999c020d6..d3377b10fc0f 100644 --- a/drivers/mtd/devices/st_spi_fsm.c +++ b/drivers/mtd/devices/st_spi_fsm.c @@ -2126,6 +2126,8 @@ static int stfsm_remove(struct platform_device *pdev) { struct stfsm *fsm = platform_get_drvdata(pdev); + clk_disable_unprepare(fsm->clk); + return mtd_device_unregister(&fsm->mtd); } diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 6a099bbcd8be..e098ae937ce8 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -300,15 +300,6 @@ config MTD_DC21285 21285 bridge used with Intel's StrongARM processors. More info at . -config MTD_IXP4XX - tristate "CFI Flash device mapped on Intel IXP4xx based systems" - depends on MTD_CFI && MTD_COMPLEX_MAPPINGS && ARCH_IXP4XX && MTD_CFI_ADV_OPTIONS - help - This enables MTD access to flash devices on platforms based - on Intel's IXP4xx family of network processors such as the - IXDP425 and Coyote. If you have an IXP4xx based board and - would like to use the flash chips on it, say 'Y'. - config MTD_IMPA7 tristate "JEDEC Flash device mapped on impA7" depends on ARM && MTD_JEDECPROBE diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index 2240b100f66a..094cfb244086 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -39,7 +39,6 @@ obj-$(CONFIG_MTD_IMPA7) += impa7.o obj-$(CONFIG_MTD_UCLINUX) += uclinux.o obj-$(CONFIG_MTD_NETtel) += nettel.o obj-$(CONFIG_MTD_SCB2_FLASH) += scb2_flash.o -obj-$(CONFIG_MTD_IXP4XX) += ixp4xx.o obj-$(CONFIG_MTD_PLATRAM) += plat-ram.o obj-$(CONFIG_MTD_INTEL_VR_NOR) += intel_vr_nor.o obj-$(CONFIG_MTD_VMU) += vmu-flash.o diff --git a/drivers/mtd/maps/ixp4xx.c b/drivers/mtd/maps/ixp4xx.c deleted file mode 100644 index d8543201ab94..000000000000 --- a/drivers/mtd/maps/ixp4xx.c +++ /dev/null @@ -1,262 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * drivers/mtd/maps/ixp4xx.c - * - * MTD Map file for IXP4XX based systems. Please do not make per-board - * changes in here. If your board needs special setup, do it in your - * platform level code in arch/arm/mach-ixp4xx/board-setup.c - * - * Original Author: Intel Corporation - * Maintainer: Deepak Saxena - * - * Copyright (C) 2002 Intel Corporation - * Copyright (C) 2003-2004 MontaVista Software, Inc. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include - -/* - * Read/write a 16 bit word from flash address 'addr'. - * - * When the cpu is in little-endian mode it swizzles the address lines - * ('address coherency') so we need to undo the swizzling to ensure commands - * and the like end up on the correct flash address. - * - * To further complicate matters, due to the way the expansion bus controller - * handles 32 bit reads, the byte stream ABCD is stored on the flash as: - * D15 D0 - * +---+---+ - * | A | B | 0 - * +---+---+ - * | C | D | 2 - * +---+---+ - * This means that on LE systems each 16 bit word must be swapped. Note that - * this requires CONFIG_MTD_CFI_BE_BYTE_SWAP to be enabled to 'unswap' the CFI - * data and other flash commands which are always in D7-D0. - */ -#ifndef __ARMEB__ -#ifndef CONFIG_MTD_CFI_BE_BYTE_SWAP -# error CONFIG_MTD_CFI_BE_BYTE_SWAP required -#endif - -static inline u16 flash_read16(void __iomem *addr) -{ - return be16_to_cpu(__raw_readw((void __iomem *)((unsigned long)addr ^ 0x2))); -} - -static inline void flash_write16(u16 d, void __iomem *addr) -{ - __raw_writew(cpu_to_be16(d), (void __iomem *)((unsigned long)addr ^ 0x2)); -} - -#define BYTE0(h) ((h) & 0xFF) -#define BYTE1(h) (((h) >> 8) & 0xFF) - -#else - -static inline u16 flash_read16(const void __iomem *addr) -{ - return __raw_readw(addr); -} - -static inline void flash_write16(u16 d, void __iomem *addr) -{ - __raw_writew(d, addr); -} - -#define BYTE0(h) (((h) >> 8) & 0xFF) -#define BYTE1(h) ((h) & 0xFF) -#endif - -static map_word ixp4xx_read16(struct map_info *map, unsigned long ofs) -{ - map_word val; - val.x[0] = flash_read16(map->virt + ofs); - return val; -} - -/* - * The IXP4xx expansion bus only allows 16-bit wide acceses - * when attached to a 16-bit wide device (such as the 28F128J3A), - * so we can't just memcpy_fromio(). - */ -static void ixp4xx_copy_from(struct map_info *map, void *to, - unsigned long from, ssize_t len) -{ - u8 *dest = (u8 *) to; - void __iomem *src = map->virt + from; - - if (len <= 0) - return; - - if (from & 1) { - *dest++ = BYTE1(flash_read16(src-1)); - src++; - --len; - } - - while (len >= 2) { - u16 data = flash_read16(src); - *dest++ = BYTE0(data); - *dest++ = BYTE1(data); - src += 2; - len -= 2; - } - - if (len > 0) - *dest++ = BYTE0(flash_read16(src)); -} - -/* - * Unaligned writes are ignored, causing the 8-bit - * probe to fail and proceed to the 16-bit probe (which succeeds). - */ -static void ixp4xx_probe_write16(struct map_info *map, map_word d, unsigned long adr) -{ - if (!(adr & 1)) - flash_write16(d.x[0], map->virt + adr); -} - -/* - * Fast write16 function without the probing check above - */ -static void ixp4xx_write16(struct map_info *map, map_word d, unsigned long adr) -{ - flash_write16(d.x[0], map->virt + adr); -} - -struct ixp4xx_flash_info { - struct mtd_info *mtd; - struct map_info map; - struct resource *res; -}; - -static const char * const probes[] = { "RedBoot", "cmdlinepart", NULL }; - -static int ixp4xx_flash_remove(struct platform_device *dev) -{ - struct flash_platform_data *plat = dev_get_platdata(&dev->dev); - struct ixp4xx_flash_info *info = platform_get_drvdata(dev); - - if(!info) - return 0; - - if (info->mtd) { - mtd_device_unregister(info->mtd); - map_destroy(info->mtd); - } - - if (plat->exit) - plat->exit(); - - return 0; -} - -static int ixp4xx_flash_probe(struct platform_device *dev) -{ - struct flash_platform_data *plat = dev_get_platdata(&dev->dev); - struct ixp4xx_flash_info *info; - struct mtd_part_parser_data ppdata = { - .origin = dev->resource->start, - }; - int err = -1; - - if (!plat) - return -ENODEV; - - if (plat->init) { - err = plat->init(); - if (err) - return err; - } - - info = devm_kzalloc(&dev->dev, sizeof(struct ixp4xx_flash_info), - GFP_KERNEL); - if(!info) { - err = -ENOMEM; - goto Error; - } - - platform_set_drvdata(dev, info); - - /* - * Tell the MTD layer we're not 1:1 mapped so that it does - * not attempt to do a direct access on us. - */ - info->map.phys = NO_XIP; - info->map.size = resource_size(dev->resource); - - /* - * We only support 16-bit accesses for now. If and when - * any board use 8-bit access, we'll fixup the driver to - * handle that. - */ - info->map.bankwidth = 2; - info->map.name = dev_name(&dev->dev); - info->map.read = ixp4xx_read16; - info->map.write = ixp4xx_probe_write16; - info->map.copy_from = ixp4xx_copy_from; - - info->map.virt = devm_ioremap_resource(&dev->dev, dev->resource); - if (IS_ERR(info->map.virt)) { - err = PTR_ERR(info->map.virt); - goto Error; - } - - info->mtd = do_map_probe(plat->map_name, &info->map); - if (!info->mtd) { - printk(KERN_ERR "IXP4XXFlash: map_probe failed\n"); - err = -ENXIO; - goto Error; - } - info->mtd->dev.parent = &dev->dev; - - /* Use the fast version */ - info->map.write = ixp4xx_write16; - - err = mtd_device_parse_register(info->mtd, probes, &ppdata, - plat->parts, plat->nr_parts); - if (err) { - printk(KERN_ERR "Could not parse partitions\n"); - goto Error; - } - - return 0; - -Error: - ixp4xx_flash_remove(dev); - return err; -} - -static struct platform_driver ixp4xx_flash_driver = { - .probe = ixp4xx_flash_probe, - .remove = ixp4xx_flash_remove, - .driver = { - .name = "IXP4XX-Flash", - }, -}; - -module_platform_driver(ixp4xx_flash_driver); - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("MTD map driver for Intel IXP4xx systems"); -MODULE_AUTHOR("Deepak Saxena"); -MODULE_ALIAS("platform:IXP4XX-Flash"); diff --git a/drivers/mtd/mtdblock.c b/drivers/mtd/mtdblock.c index 03e3de3a5d79..1e94e7d10b8b 100644 --- a/drivers/mtd/mtdblock.c +++ b/drivers/mtd/mtdblock.c @@ -257,6 +257,10 @@ static int mtdblock_open(struct mtd_blktrans_dev *mbd) return 0; } + if (mtd_type_is_nand(mbd->mtd)) + pr_warn("%s: MTD device '%s' is NAND, please consider using UBI block devices instead.\n", + mbd->tr->name, mbd->mtd->name); + /* OK, it's not open. Create cache info for it */ mtdblk->count = 1; mutex_init(&mtdblk->cache_mutex); @@ -322,10 +326,6 @@ static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) if (!(mtd->flags & MTD_WRITEABLE)) dev->mbd.readonly = 1; - if (mtd_type_is_nand(mtd)) - pr_warn("%s: MTD device '%s' is NAND, please consider using UBI block devices instead.\n", - tr->name, mtd->name); - if (add_mtd_blktrans_dev(&dev->mbd)) kfree(dev); } diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 7731796024e0..9eb0680db312 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -557,9 +557,10 @@ static int mtd_nvmem_add(struct mtd_info *mtd) int add_mtd_device(struct mtd_info *mtd) { + struct device_node *np = mtd_get_of_node(mtd); struct mtd_info *master = mtd_get_master(mtd); struct mtd_notifier *not; - int i, error; + int i, error, ofidx; /* * May occur, for instance, on buggy drivers which call @@ -598,7 +599,13 @@ int add_mtd_device(struct mtd_info *mtd) mutex_lock(&mtd_table_mutex); - i = idr_alloc(&mtd_idr, mtd, 0, 0, GFP_KERNEL); + ofidx = -1; + if (np) + ofidx = of_alias_get_id(np, "mtd"); + if (ofidx >= 0) + i = idr_alloc(&mtd_idr, mtd, ofidx, ofidx + 1, GFP_KERNEL); + else + i = idr_alloc(&mtd_idr, mtd, 0, 0, GFP_KERNEL); if (i < 0) { error = i; goto fail_locked; diff --git a/drivers/mtd/mtdoops.c b/drivers/mtd/mtdoops.c index 227df24387df..3d4a2ffb5b01 100644 --- a/drivers/mtd/mtdoops.c +++ b/drivers/mtd/mtdoops.c @@ -16,15 +16,13 @@ #include #include #include +#include #include #include /* Maximum MTD partition size */ #define MTDOOPS_MAX_MTD_SIZE (8 * 1024 * 1024) -#define MTDOOPS_KERNMSG_MAGIC 0x5d005d00 -#define MTDOOPS_HEADER_SIZE 8 - static unsigned long record_size = 4096; module_param(record_size, ulong, 0400); MODULE_PARM_DESC(record_size, @@ -40,6 +38,15 @@ module_param(dump_oops, int, 0600); MODULE_PARM_DESC(dump_oops, "set to 1 to dump oopses, 0 to only dump panics (default 1)"); +#define MTDOOPS_KERNMSG_MAGIC_v1 0x5d005d00 /* Original */ +#define MTDOOPS_KERNMSG_MAGIC_v2 0x5d005e00 /* Adds the timestamp */ + +struct mtdoops_hdr { + u32 seq; + u32 magic; + ktime_t timestamp; +} __packed; + static struct mtdoops_context { struct kmsg_dumper dump; @@ -178,16 +185,17 @@ static void mtdoops_write(struct mtdoops_context *cxt, int panic) { struct mtd_info *mtd = cxt->mtd; size_t retlen; - u32 *hdr; + struct mtdoops_hdr *hdr; int ret; if (test_and_set_bit(0, &cxt->oops_buf_busy)) return; /* Add mtdoops header to the buffer */ - hdr = cxt->oops_buf; - hdr[0] = cxt->nextcount; - hdr[1] = MTDOOPS_KERNMSG_MAGIC; + hdr = (struct mtdoops_hdr *)cxt->oops_buf; + hdr->seq = cxt->nextcount; + hdr->magic = MTDOOPS_KERNMSG_MAGIC_v2; + hdr->timestamp = ktime_get_real(); if (panic) { ret = mtd_panic_write(mtd, cxt->nextpage * record_size, @@ -222,8 +230,9 @@ static void mtdoops_workfunc_write(struct work_struct *work) static void find_next_position(struct mtdoops_context *cxt) { struct mtd_info *mtd = cxt->mtd; + struct mtdoops_hdr hdr; int ret, page, maxpos = 0; - u32 count[2], maxcount = 0xffffffff; + u32 maxcount = 0xffffffff; size_t retlen; for (page = 0; page < cxt->oops_pages; page++) { @@ -231,32 +240,33 @@ static void find_next_position(struct mtdoops_context *cxt) continue; /* Assume the page is used */ mark_page_used(cxt, page); - ret = mtd_read(mtd, page * record_size, MTDOOPS_HEADER_SIZE, - &retlen, (u_char *)&count[0]); - if (retlen != MTDOOPS_HEADER_SIZE || + ret = mtd_read(mtd, page * record_size, sizeof(hdr), + &retlen, (u_char *)&hdr); + if (retlen != sizeof(hdr) || (ret < 0 && !mtd_is_bitflip(ret))) { - printk(KERN_ERR "mtdoops: read failure at %ld (%td of %d read), err %d\n", - page * record_size, retlen, - MTDOOPS_HEADER_SIZE, ret); + printk(KERN_ERR "mtdoops: read failure at %ld (%zu of %zu read), err %d\n", + page * record_size, retlen, sizeof(hdr), ret); continue; } - if (count[0] == 0xffffffff && count[1] == 0xffffffff) + if (hdr.seq == 0xffffffff && hdr.magic == 0xffffffff) mark_page_unused(cxt, page); - if (count[0] == 0xffffffff || count[1] != MTDOOPS_KERNMSG_MAGIC) + if (hdr.seq == 0xffffffff || + (hdr.magic != MTDOOPS_KERNMSG_MAGIC_v1 && + hdr.magic != MTDOOPS_KERNMSG_MAGIC_v2)) continue; if (maxcount == 0xffffffff) { - maxcount = count[0]; + maxcount = hdr.seq; maxpos = page; - } else if (count[0] < 0x40000000 && maxcount > 0xc0000000) { - maxcount = count[0]; + } else if (hdr.seq < 0x40000000 && maxcount > 0xc0000000) { + maxcount = hdr.seq; maxpos = page; - } else if (count[0] > maxcount && count[0] < 0xc0000000) { - maxcount = count[0]; + } else if (hdr.seq > maxcount && hdr.seq < 0xc0000000) { + maxcount = hdr.seq; maxpos = page; - } else if (count[0] > maxcount && count[0] > 0xc0000000 + } else if (hdr.seq > maxcount && hdr.seq > 0xc0000000 && maxcount > 0x80000000) { - maxcount = count[0]; + maxcount = hdr.seq; maxpos = page; } } @@ -287,8 +297,9 @@ static void mtdoops_do_dump(struct kmsg_dumper *dumper, if (test_and_set_bit(0, &cxt->oops_buf_busy)) return; - kmsg_dump_get_buffer(&iter, true, cxt->oops_buf + MTDOOPS_HEADER_SIZE, - record_size - MTDOOPS_HEADER_SIZE, NULL); + kmsg_dump_get_buffer(&iter, true, + cxt->oops_buf + sizeof(struct mtdoops_hdr), + record_size - sizeof(struct mtdoops_hdr), NULL); clear_bit(0, &cxt->oops_buf_busy); if (reason != KMSG_DUMP_OOPS) { diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 357661b62c94..d442fa94c872 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "mtdcore.h" @@ -577,10 +578,16 @@ static int mtd_part_of_parse(struct mtd_info *master, struct mtd_part_parser *parser; struct device_node *np; struct property *prop; + struct device *dev; const char *compat; const char *fixed = "fixed-partitions"; int ret, err = 0; + dev = &master->dev; + /* Use parent device (controller) if the top level MTD is not registered */ + if (!IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) && !mtd_is_partition(master)) + dev = master->dev.parent; + np = mtd_get_of_node(master); if (mtd_is_partition(master)) of_node_get(np); @@ -593,6 +600,7 @@ static int mtd_part_of_parse(struct mtd_info *master, continue; ret = mtd_part_do_parse(parser, master, pparts, NULL); if (ret > 0) { + of_platform_populate(np, NULL, NULL, dev); of_node_put(np); return ret; } @@ -600,6 +608,7 @@ static int mtd_part_of_parse(struct mtd_info *master, if (ret < 0 && !err) err = ret; } + of_platform_populate(np, NULL, NULL, dev); of_node_put(np); /* diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 9b249826ef93..5b0c2c95f10c 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -53,6 +53,14 @@ config MTD_NAND_ECC_MXIC help This enables support for the hardware ECC engine from Macronix. +config MTD_NAND_ECC_MEDIATEK + tristate "Mediatek hardware ECC engine" + depends on HAS_IOMEM + depends on ARCH_MEDIATEK || COMPILE_TEST + select MTD_NAND_ECC + help + This enables support for the hardware ECC engine from Mediatek. + endmenu endmenu diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index a4e6b7ae0614..19e1291ac4d5 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -2,6 +2,7 @@ nandcore-objs := core.o bbt.o obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o +obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o obj-y += onenand/ obj-y += raw/ diff --git a/drivers/mtd/nand/raw/mtk_ecc.c b/drivers/mtd/nand/ecc-mtk.c similarity index 98% rename from drivers/mtd/nand/raw/mtk_ecc.c rename to drivers/mtd/nand/ecc-mtk.c index 49ab3448b9b1..9f9b201fe706 100644 --- a/drivers/mtd/nand/raw/mtk_ecc.c +++ b/drivers/mtd/nand/ecc-mtk.c @@ -15,8 +15,7 @@ #include #include #include - -#include "mtk_ecc.h" +#include #define ECC_IDLE_MASK BIT(0) #define ECC_IRQ_EN BIT(0) @@ -280,7 +279,10 @@ struct mtk_ecc *of_mtk_ecc_get(struct device_node *of_node) struct mtk_ecc *ecc = NULL; struct device_node *np; - np = of_parse_phandle(of_node, "ecc-engine", 0); + np = of_parse_phandle(of_node, "nand-ecc-engine", 0); + /* for backward compatibility */ + if (!np) + np = of_parse_phandle(of_node, "ecc-engine", 0); if (np) { ecc = mtk_ecc_get(np); of_node_put(np); diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig index 9b078e78f3fa..8b6d7a515445 100644 --- a/drivers/mtd/nand/raw/Kconfig +++ b/drivers/mtd/nand/raw/Kconfig @@ -374,6 +374,7 @@ config MTD_NAND_QCOM config MTD_NAND_MTK tristate "MTK NAND controller" + depends on MTD_NAND_ECC_MEDIATEK depends on ARCH_MEDIATEK || COMPILE_TEST depends on HAS_IOMEM help diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile index 88a566513c56..fa1d00120310 100644 --- a/drivers/mtd/nand/raw/Makefile +++ b/drivers/mtd/nand/raw/Makefile @@ -48,7 +48,7 @@ obj-$(CONFIG_MTD_NAND_SUNXI) += sunxi_nand.o obj-$(CONFIG_MTD_NAND_HISI504) += hisi504_nand.o obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand/ obj-$(CONFIG_MTD_NAND_QCOM) += qcom_nandc.o -obj-$(CONFIG_MTD_NAND_MTK) += mtk_ecc.o mtk_nand.o +obj-$(CONFIG_MTD_NAND_MTK) += mtk_nand.o obj-$(CONFIG_MTD_NAND_MXIC) += mxic_nand.o obj-$(CONFIG_MTD_NAND_TEGRA) += tegra_nand.o obj-$(CONFIG_MTD_NAND_STM32_FMC2) += stm32_fmc2_nand.o diff --git a/drivers/mtd/nand/raw/cadence-nand-controller.c b/drivers/mtd/nand/raw/cadence-nand-controller.c index 7eec60ea9056..0d72672f8b64 100644 --- a/drivers/mtd/nand/raw/cadence-nand-controller.c +++ b/drivers/mtd/nand/raw/cadence-nand-controller.c @@ -2983,11 +2983,10 @@ static int cadence_nand_dt_probe(struct platform_device *ofdev) if (IS_ERR(cdns_ctrl->reg)) return PTR_ERR(cdns_ctrl->reg); - res = platform_get_resource(ofdev, IORESOURCE_MEM, 1); - cdns_ctrl->io.dma = res->start; - cdns_ctrl->io.virt = devm_ioremap_resource(&ofdev->dev, res); + cdns_ctrl->io.virt = devm_platform_get_and_ioremap_resource(ofdev, 1, &res); if (IS_ERR(cdns_ctrl->io.virt)) return PTR_ERR(cdns_ctrl->io.virt); + cdns_ctrl->io.dma = res->start; dt->clk = devm_clk_get(cdns_ctrl->dev, "nf_clk"); if (IS_ERR(dt->clk)) diff --git a/drivers/mtd/nand/raw/cs553x_nand.c b/drivers/mtd/nand/raw/cs553x_nand.c index 6edf78c16fc8..f0a15717cf05 100644 --- a/drivers/mtd/nand/raw/cs553x_nand.c +++ b/drivers/mtd/nand/raw/cs553x_nand.c @@ -104,17 +104,12 @@ static int cs553x_write_ctrl_byte(struct cs553x_nand_controller *cs553x, u32 ctl, u8 data) { u8 status; - int ret; writeb(ctl, cs553x->mmio + MM_NAND_CTL); writeb(data, cs553x->mmio + MM_NAND_IO); - ret = readb_poll_timeout_atomic(cs553x->mmio + MM_NAND_STS, status, + return readb_poll_timeout_atomic(cs553x->mmio + MM_NAND_STS, status, !(status & CS_NAND_CTLR_BUSY), 1, 100000); - if (ret) - return ret; - - return 0; } static void cs553x_data_in(struct cs553x_nand_controller *cs553x, void *buf, diff --git a/drivers/mtd/nand/raw/davinci_nand.c b/drivers/mtd/nand/raw/davinci_nand.c index 45fec8c192ab..3e98e3c255bf 100644 --- a/drivers/mtd/nand/raw/davinci_nand.c +++ b/drivers/mtd/nand/raw/davinci_nand.c @@ -727,7 +727,7 @@ static int nand_davinci_probe(struct platform_device *pdev) return -ENODEV; /* which external chipselect will we be managing? */ - if (pdata->core_chipsel < 0 || pdata->core_chipsel > 3) + if (pdata->core_chipsel > 3) return -ENODEV; info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); diff --git a/drivers/mtd/nand/raw/denali_pci.c b/drivers/mtd/nand/raw/denali_pci.c index 20c085a30adc..de7e722d3826 100644 --- a/drivers/mtd/nand/raw/denali_pci.c +++ b/drivers/mtd/nand/raw/denali_pci.c @@ -74,22 +74,21 @@ static int denali_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) return ret; } - denali->reg = ioremap(csr_base, csr_len); + denali->reg = devm_ioremap(denali->dev, csr_base, csr_len); if (!denali->reg) { dev_err(&dev->dev, "Spectra: Unable to remap memory region\n"); return -ENOMEM; } - denali->host = ioremap(mem_base, mem_len); + denali->host = devm_ioremap(denali->dev, mem_base, mem_len); if (!denali->host) { dev_err(&dev->dev, "Spectra: ioremap failed!"); - ret = -ENOMEM; - goto out_unmap_reg; + return -ENOMEM; } ret = denali_init(denali); if (ret) - goto out_unmap_host; + return ret; nsels = denali->nbanks; @@ -117,10 +116,6 @@ static int denali_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) out_remove_denali: denali_remove(denali); -out_unmap_host: - iounmap(denali->host); -out_unmap_reg: - iounmap(denali->reg); return ret; } @@ -129,8 +124,6 @@ static void denali_pci_remove(struct pci_dev *dev) struct denali_controller *denali = pci_get_drvdata(dev); denali_remove(denali); - iounmap(denali->reg); - iounmap(denali->host); } static struct pci_driver denali_pci_driver = { diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c index 44b14c9dc9a7..0b68d05846e1 100644 --- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c +++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c @@ -218,7 +218,8 @@ static void gpmi_dump_info(struct gpmi_nand_data *this) "ECC Strength : %u\n" "Page Size in Bytes : %u\n" "Metadata Size in Bytes : %u\n" - "ECC Chunk Size in Bytes: %u\n" + "ECC0 Chunk Size in Bytes: %u\n" + "ECCn Chunk Size in Bytes: %u\n" "ECC Chunk Count : %u\n" "Payload Size in Bytes : %u\n" "Auxiliary Size in Bytes: %u\n" @@ -229,7 +230,8 @@ static void gpmi_dump_info(struct gpmi_nand_data *this) geo->ecc_strength, geo->page_size, geo->metadata_size, - geo->ecc_chunk_size, + geo->ecc0_chunk_size, + geo->eccn_chunk_size, geo->ecc_chunk_count, geo->payload_size, geo->auxiliary_size, @@ -238,9 +240,15 @@ static void gpmi_dump_info(struct gpmi_nand_data *this) geo->block_mark_bit_offset); } -static inline bool gpmi_check_ecc(struct gpmi_nand_data *this) +static bool gpmi_check_ecc(struct gpmi_nand_data *this) { + struct nand_chip *chip = &this->nand; struct bch_geometry *geo = &this->bch_geometry; + struct nand_device *nand = &chip->base; + struct nand_ecc_props *conf = &nand->ecc.ctx.conf; + + conf->step_size = geo->eccn_chunk_size; + conf->strength = geo->ecc_strength; /* Do the sanity check. */ if (GPMI_IS_MXS(this)) { @@ -248,7 +256,47 @@ static inline bool gpmi_check_ecc(struct gpmi_nand_data *this) if (geo->gf_len == 14) return false; } - return geo->ecc_strength <= this->devdata->bch_max_ecc_strength; + + if (geo->ecc_strength > this->devdata->bch_max_ecc_strength) + return false; + + if (!nand_ecc_is_strong_enough(nand)) + return false; + + return true; +} + +/* check if bbm locates in data chunk rather than ecc chunk */ +static bool bbm_in_data_chunk(struct gpmi_nand_data *this, + unsigned int *chunk_num) +{ + struct bch_geometry *geo = &this->bch_geometry; + struct nand_chip *chip = &this->nand; + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int i, j; + + if (geo->ecc0_chunk_size != geo->eccn_chunk_size) { + dev_err(this->dev, + "The size of ecc0_chunk must equal to eccn_chunk\n"); + return false; + } + + i = (mtd->writesize * 8 - geo->metadata_size * 8) / + (geo->gf_len * geo->ecc_strength + + geo->eccn_chunk_size * 8); + + j = (mtd->writesize * 8 - geo->metadata_size * 8) - + (geo->gf_len * geo->ecc_strength + + geo->eccn_chunk_size * 8) * i; + + if (j < geo->eccn_chunk_size * 8) { + *chunk_num = i+1; + dev_dbg(this->dev, "Set ecc to %d and bbm in chunk %d\n", + geo->ecc_strength, *chunk_num); + return true; + } + + return false; } /* @@ -280,13 +328,14 @@ static int set_geometry_by_ecc_info(struct gpmi_nand_data *this, nanddev_get_ecc_requirements(&chip->base)->step_size); return -EINVAL; } - geo->ecc_chunk_size = ecc_step; + geo->ecc0_chunk_size = ecc_step; + geo->eccn_chunk_size = ecc_step; geo->ecc_strength = round_up(ecc_strength, 2); if (!gpmi_check_ecc(this)) return -EINVAL; /* Keep the C >= O */ - if (geo->ecc_chunk_size < mtd->oobsize) { + if (geo->eccn_chunk_size < mtd->oobsize) { dev_err(this->dev, "unsupported nand chip. ecc size: %d, oob size : %d\n", ecc_step, mtd->oobsize); @@ -296,7 +345,7 @@ static int set_geometry_by_ecc_info(struct gpmi_nand_data *this, /* The default value, see comment in the legacy_set_geometry(). */ geo->metadata_size = 10; - geo->ecc_chunk_count = mtd->writesize / geo->ecc_chunk_size; + geo->ecc_chunk_count = mtd->writesize / geo->eccn_chunk_size; /* * Now, the NAND chip with 2K page(data chunk is 512byte) shows below: @@ -399,6 +448,134 @@ static inline int get_ecc_strength(struct gpmi_nand_data *this) return round_down(ecc_strength, 2); } +static int set_geometry_for_large_oob(struct gpmi_nand_data *this) +{ + struct bch_geometry *geo = &this->bch_geometry; + struct nand_chip *chip = &this->nand; + struct mtd_info *mtd = nand_to_mtd(chip); + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(&chip->base); + unsigned int block_mark_bit_offset; + unsigned int max_ecc; + unsigned int bbm_chunk; + unsigned int i; + + /* sanity check for the minimum ecc nand required */ + if (!(requirements->strength > 0 && + requirements->step_size > 0)) + return -EINVAL; + geo->ecc_strength = requirements->strength; + + /* check if platform can support this nand */ + if (!gpmi_check_ecc(this)) { + dev_err(this->dev, + "unsupported NAND chip, minimum ecc required %d\n", + geo->ecc_strength); + return -EINVAL; + } + + /* calculate the maximum ecc platform can support*/ + geo->metadata_size = 10; + geo->gf_len = 14; + geo->ecc0_chunk_size = 1024; + geo->eccn_chunk_size = 1024; + geo->ecc_chunk_count = mtd->writesize / geo->eccn_chunk_size; + max_ecc = min(get_ecc_strength(this), + this->devdata->bch_max_ecc_strength); + + /* + * search a supported ecc strength that makes bbm + * located in data chunk + */ + geo->ecc_strength = max_ecc; + while (!(geo->ecc_strength < requirements->strength)) { + if (bbm_in_data_chunk(this, &bbm_chunk)) + goto geo_setting; + geo->ecc_strength -= 2; + } + + /* if none of them works, keep using the minimum ecc */ + /* nand required but changing ecc page layout */ + geo->ecc_strength = requirements->strength; + /* add extra ecc for meta data */ + geo->ecc0_chunk_size = 0; + geo->ecc_chunk_count = (mtd->writesize / geo->eccn_chunk_size) + 1; + geo->ecc_for_meta = 1; + /* check if oob can afford this extra ecc chunk */ + if (mtd->oobsize * 8 < geo->metadata_size * 8 + + geo->gf_len * geo->ecc_strength * geo->ecc_chunk_count) { + dev_err(this->dev, "unsupported NAND chip with new layout\n"); + return -EINVAL; + } + + /* calculate in which chunk bbm located */ + bbm_chunk = (mtd->writesize * 8 - geo->metadata_size * 8 - + geo->gf_len * geo->ecc_strength) / + (geo->gf_len * geo->ecc_strength + + geo->eccn_chunk_size * 8) + 1; + +geo_setting: + + geo->page_size = mtd->writesize + geo->metadata_size + + (geo->gf_len * geo->ecc_strength * geo->ecc_chunk_count) / 8; + geo->payload_size = mtd->writesize; + + /* + * The auxiliary buffer contains the metadata and the ECC status. The + * metadata is padded to the nearest 32-bit boundary. The ECC status + * contains one byte for every ECC chunk, and is also padded to the + * nearest 32-bit boundary. + */ + geo->auxiliary_status_offset = ALIGN(geo->metadata_size, 4); + geo->auxiliary_size = ALIGN(geo->metadata_size, 4) + + ALIGN(geo->ecc_chunk_count, 4); + + if (!this->swap_block_mark) + return 0; + + /* calculate the number of ecc chunk behind the bbm */ + i = (mtd->writesize / geo->eccn_chunk_size) - bbm_chunk + 1; + + block_mark_bit_offset = mtd->writesize * 8 - + (geo->ecc_strength * geo->gf_len * (geo->ecc_chunk_count - i) + + geo->metadata_size * 8); + + geo->block_mark_byte_offset = block_mark_bit_offset / 8; + geo->block_mark_bit_offset = block_mark_bit_offset % 8; + + dev_dbg(this->dev, "BCH Geometry :\n" + "GF length : %u\n" + "ECC Strength : %u\n" + "Page Size in Bytes : %u\n" + "Metadata Size in Bytes : %u\n" + "ECC0 Chunk Size in Bytes: %u\n" + "ECCn Chunk Size in Bytes: %u\n" + "ECC Chunk Count : %u\n" + "Payload Size in Bytes : %u\n" + "Auxiliary Size in Bytes: %u\n" + "Auxiliary Status Offset: %u\n" + "Block Mark Byte Offset : %u\n" + "Block Mark Bit Offset : %u\n" + "Block Mark in chunk : %u\n" + "Ecc for Meta data : %u\n", + geo->gf_len, + geo->ecc_strength, + geo->page_size, + geo->metadata_size, + geo->ecc0_chunk_size, + geo->eccn_chunk_size, + geo->ecc_chunk_count, + geo->payload_size, + geo->auxiliary_size, + geo->auxiliary_status_offset, + geo->block_mark_byte_offset, + geo->block_mark_bit_offset, + bbm_chunk, + geo->ecc_for_meta); + + return 0; +} + static int legacy_set_geometry(struct gpmi_nand_data *this) { struct bch_geometry *geo = &this->bch_geometry; @@ -418,13 +595,15 @@ static int legacy_set_geometry(struct gpmi_nand_data *this) geo->gf_len = 13; /* The default for chunk size. */ - geo->ecc_chunk_size = 512; - while (geo->ecc_chunk_size < mtd->oobsize) { - geo->ecc_chunk_size *= 2; /* keep C >= O */ + geo->ecc0_chunk_size = 512; + geo->eccn_chunk_size = 512; + while (geo->eccn_chunk_size < mtd->oobsize) { + geo->ecc0_chunk_size *= 2; /* keep C >= O */ + geo->eccn_chunk_size *= 2; /* keep C >= O */ geo->gf_len = 14; } - geo->ecc_chunk_count = mtd->writesize / geo->ecc_chunk_size; + geo->ecc_chunk_count = mtd->writesize / geo->eccn_chunk_size; /* We use the same ECC strength for all chunks. */ geo->ecc_strength = get_ecc_strength(this); @@ -514,24 +693,40 @@ static int legacy_set_geometry(struct gpmi_nand_data *this) static int common_nfc_set_geometry(struct gpmi_nand_data *this) { struct nand_chip *chip = &this->nand; + struct mtd_info *mtd = nand_to_mtd(&this->nand); const struct nand_ecc_props *requirements = nanddev_get_ecc_requirements(&chip->base); + bool use_minimun_ecc; + int err; - if (chip->ecc.strength > 0 && chip->ecc.size > 0) - return set_geometry_by_ecc_info(this, chip->ecc.strength, - chip->ecc.size); + use_minimun_ecc = of_property_read_bool(this->dev->of_node, + "fsl,use-minimum-ecc"); - if ((of_property_read_bool(this->dev->of_node, "fsl,use-minimum-ecc")) - || legacy_set_geometry(this)) { - if (!(requirements->strength > 0 && requirements->step_size > 0)) - return -EINVAL; - - return set_geometry_by_ecc_info(this, - requirements->strength, - requirements->step_size); + /* use legacy bch geometry settings by default*/ + if ((!use_minimun_ecc && mtd->oobsize < 1024) || + !(requirements->strength > 0 && requirements->step_size > 0)) { + dev_dbg(this->dev, "use legacy bch geometry\n"); + err = legacy_set_geometry(this); + if (!err) + return 0; } - return 0; + /* for large oob nand */ + if (mtd->oobsize > 1024) { + dev_dbg(this->dev, "use large oob bch geometry\n"); + err = set_geometry_for_large_oob(this); + if (!err) + return 0; + } + + /* otherwise use the minimum ecc nand chip required */ + dev_dbg(this->dev, "use minimum ecc bch geometry\n"); + err = set_geometry_by_ecc_info(this, requirements->strength, + requirements->step_size); + if (err) + dev_err(this->dev, "none of the bch geometry setting works\n"); + + return err; } /* Configures the geometry for BCH. */ @@ -843,7 +1038,7 @@ static int gpmi_raw_len_to_len(struct gpmi_nand_data *this, int raw_len) * we are passed in exec_op. Calculate the data length from it. */ if (this->bch) - return ALIGN_DOWN(raw_len, this->bch_geometry.ecc_chunk_size); + return ALIGN_DOWN(raw_len, this->bch_geometry.eccn_chunk_size); else return raw_len; } @@ -1235,7 +1430,7 @@ static int gpmi_count_bitflips(struct nand_chip *chip, void *buf, int first, /* Read ECC bytes into our internal raw_buffer */ offset = nfc_geo->metadata_size * 8; - offset += ((8 * nfc_geo->ecc_chunk_size) + eccbits) * (i + 1); + offset += ((8 * nfc_geo->eccn_chunk_size) + eccbits) * (i + 1); offset -= eccbits; bitoffset = offset % 8; eccbytes = DIV_ROUND_UP(offset + eccbits, 8); @@ -1272,16 +1467,16 @@ static int gpmi_count_bitflips(struct nand_chip *chip, void *buf, int first, if (i == 0) { /* The first block includes metadata */ flips = nand_check_erased_ecc_chunk( - buf + i * nfc_geo->ecc_chunk_size, - nfc_geo->ecc_chunk_size, + buf + i * nfc_geo->eccn_chunk_size, + nfc_geo->eccn_chunk_size, eccbuf, eccbytes, this->auxiliary_virt, nfc_geo->metadata_size, nfc_geo->ecc_strength); } else { flips = nand_check_erased_ecc_chunk( - buf + i * nfc_geo->ecc_chunk_size, - nfc_geo->ecc_chunk_size, + buf + i * nfc_geo->eccn_chunk_size, + nfc_geo->eccn_chunk_size, eccbuf, eccbytes, NULL, 0, nfc_geo->ecc_strength); @@ -1310,20 +1505,21 @@ static void gpmi_bch_layout_std(struct gpmi_nand_data *this) struct bch_geometry *geo = &this->bch_geometry; unsigned int ecc_strength = geo->ecc_strength >> 1; unsigned int gf_len = geo->gf_len; - unsigned int block_size = geo->ecc_chunk_size; + unsigned int block0_size = geo->ecc0_chunk_size; + unsigned int blockn_size = geo->eccn_chunk_size; this->bch_flashlayout0 = BF_BCH_FLASH0LAYOUT0_NBLOCKS(geo->ecc_chunk_count - 1) | BF_BCH_FLASH0LAYOUT0_META_SIZE(geo->metadata_size) | BF_BCH_FLASH0LAYOUT0_ECC0(ecc_strength, this) | BF_BCH_FLASH0LAYOUT0_GF(gf_len, this) | - BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(block_size, this); + BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(block0_size, this); this->bch_flashlayout1 = BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(geo->page_size) | BF_BCH_FLASH0LAYOUT1_ECCN(ecc_strength, this) | BF_BCH_FLASH0LAYOUT1_GF(gf_len, this) | - BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(block_size, this); + BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(blockn_size, this); } static int gpmi_ecc_read_page(struct nand_chip *chip, uint8_t *buf, @@ -1406,29 +1602,49 @@ static int gpmi_ecc_read_subpage(struct nand_chip *chip, uint32_t offs, } } + /* + * if there is an ECC dedicate for meta: + * - need to add an extra ECC size when calculating col and page_size, + * if the meta size is NOT zero. + * - ecc0_chunk size need to set to the same size as other chunks, + * if the meta size is zero. + */ + meta = geo->metadata_size; if (first) { - col = meta + (size + ecc_parity_size) * first; + if (geo->ecc_for_meta) + col = meta + ecc_parity_size + + (size + ecc_parity_size) * first; + else + col = meta + (size + ecc_parity_size) * first; + meta = 0; buf = buf + first * size; } ecc_parity_size = geo->gf_len * geo->ecc_strength / 8; - n = last - first + 1; - page_size = meta + (size + ecc_parity_size) * n; + + if (geo->ecc_for_meta && meta) + page_size = meta + ecc_parity_size + + (size + ecc_parity_size) * n; + else + page_size = meta + (size + ecc_parity_size) * n; + ecc_strength = geo->ecc_strength >> 1; - this->bch_flashlayout0 = BF_BCH_FLASH0LAYOUT0_NBLOCKS(n - 1) | + this->bch_flashlayout0 = BF_BCH_FLASH0LAYOUT0_NBLOCKS( + (geo->ecc_for_meta ? n : n - 1)) | BF_BCH_FLASH0LAYOUT0_META_SIZE(meta) | BF_BCH_FLASH0LAYOUT0_ECC0(ecc_strength, this) | BF_BCH_FLASH0LAYOUT0_GF(geo->gf_len, this) | - BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(geo->ecc_chunk_size, this); + BF_BCH_FLASH0LAYOUT0_DATA0_SIZE((geo->ecc_for_meta ? + 0 : geo->ecc0_chunk_size), this); this->bch_flashlayout1 = BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(page_size) | BF_BCH_FLASH0LAYOUT1_ECCN(ecc_strength, this) | BF_BCH_FLASH0LAYOUT1_GF(geo->gf_len, this) | - BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(geo->ecc_chunk_size, this); + BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(geo->eccn_chunk_size, this); this->bch = true; @@ -1597,7 +1813,7 @@ static int gpmi_ecc_read_page_raw(struct nand_chip *chip, uint8_t *buf, struct mtd_info *mtd = nand_to_mtd(chip); struct gpmi_nand_data *this = nand_get_controller_data(chip); struct bch_geometry *nfc_geo = &this->bch_geometry; - int eccsize = nfc_geo->ecc_chunk_size; + int eccsize = nfc_geo->eccn_chunk_size; int eccbits = nfc_geo->ecc_strength * nfc_geo->gf_len; u8 *tmp_buf = this->raw_buffer; size_t src_bit_off; @@ -1682,7 +1898,7 @@ static int gpmi_ecc_write_page_raw(struct nand_chip *chip, const uint8_t *buf, struct mtd_info *mtd = nand_to_mtd(chip); struct gpmi_nand_data *this = nand_get_controller_data(chip); struct bch_geometry *nfc_geo = &this->bch_geometry; - int eccsize = nfc_geo->ecc_chunk_size; + int eccsize = nfc_geo->eccn_chunk_size; int eccbits = nfc_geo->ecc_strength * nfc_geo->gf_len; u8 *tmp_buf = this->raw_buffer; uint8_t *oob = chip->oob_poi; @@ -2056,7 +2272,7 @@ static int gpmi_init_last(struct gpmi_nand_data *this) ecc->read_oob_raw = gpmi_ecc_read_oob_raw; ecc->write_oob_raw = gpmi_ecc_write_oob_raw; ecc->engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; - ecc->size = bch_geo->ecc_chunk_size; + ecc->size = bch_geo->eccn_chunk_size; ecc->strength = bch_geo->ecc_strength; mtd_set_ooblayout(mtd, &gpmi_ooblayout_ops); diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h index 5e1c3ddae5f8..c3ff56ac62a7 100644 --- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h +++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h @@ -30,9 +30,9 @@ struct resources { * @page_size: The size, in bytes, of a physical page, including * both data and OOB. * @metadata_size: The size, in bytes, of the metadata. - * @ecc_chunk_size: The size, in bytes, of a single ECC chunk. Note - * the first chunk in the page includes both data and - * metadata, so it's a bit larger than this value. + * @ecc0_chunk_size: The size, in bytes, of a first ECC chunk. + * @eccn_chunk_size: The size, in bytes, of a single ECC chunk after + * the first chunk in the page. * @ecc_chunk_count: The number of ECC chunks in the page, * @payload_size: The size, in bytes, of the payload buffer. * @auxiliary_size: The size, in bytes, of the auxiliary buffer. @@ -42,19 +42,23 @@ struct resources { * which the underlying physical block mark appears. * @block_mark_bit_offset: The bit offset into the ECC-based page view at * which the underlying physical block mark appears. + * @ecc_for_meta: The flag to indicate if there is a dedicate ecc + * for meta. */ struct bch_geometry { unsigned int gf_len; unsigned int ecc_strength; unsigned int page_size; unsigned int metadata_size; - unsigned int ecc_chunk_size; + unsigned int ecc0_chunk_size; + unsigned int eccn_chunk_size; unsigned int ecc_chunk_count; unsigned int payload_size; unsigned int auxiliary_size; unsigned int auxiliary_status_offset; unsigned int block_mark_byte_offset; unsigned int block_mark_bit_offset; + unsigned int ecc_for_meta; /* ECC for meta data */ }; /** diff --git a/drivers/mtd/nand/raw/intel-nand-controller.c b/drivers/mtd/nand/raw/intel-nand-controller.c index 7c1c80dae826..e91b879b32bd 100644 --- a/drivers/mtd/nand/raw/intel-nand-controller.c +++ b/drivers/mtd/nand/raw/intel-nand-controller.c @@ -619,9 +619,9 @@ static int ebu_nand_probe(struct platform_device *pdev) resname = devm_kasprintf(dev, GFP_KERNEL, "nand_cs%d", cs); res = platform_get_resource_byname(pdev, IORESOURCE_MEM, resname); ebu_host->cs[cs].chipaddr = devm_ioremap_resource(dev, res); - ebu_host->cs[cs].nand_pa = res->start; if (IS_ERR(ebu_host->cs[cs].chipaddr)) return PTR_ERR(ebu_host->cs[cs].chipaddr); + ebu_host->cs[cs].nand_pa = res->start; ebu_host->clk = devm_clk_get(dev, NULL); if (IS_ERR(ebu_host->clk)) diff --git a/drivers/mtd/nand/raw/mpc5121_nfc.c b/drivers/mtd/nand/raw/mpc5121_nfc.c index 5b9271b9c326..800d774aed8e 100644 --- a/drivers/mtd/nand/raw/mpc5121_nfc.c +++ b/drivers/mtd/nand/raw/mpc5121_nfc.c @@ -595,8 +595,7 @@ static void mpc5121_nfc_free(struct device *dev, struct mtd_info *mtd) struct nand_chip *chip = mtd_to_nand(mtd); struct mpc5121_nfc_prv *prv = nand_get_controller_data(chip); - if (prv->clk) - clk_disable_unprepare(prv->clk); + clk_disable_unprepare(prv->clk); if (prv->csreg) iounmap(prv->csreg); diff --git a/drivers/mtd/nand/raw/mtk_nand.c b/drivers/mtd/nand/raw/mtk_nand.c index 66f04c693c87..d540454cbbdf 100644 --- a/drivers/mtd/nand/raw/mtk_nand.c +++ b/drivers/mtd/nand/raw/mtk_nand.c @@ -17,7 +17,7 @@ #include #include #include -#include "mtk_ecc.h" +#include /* NAND controller register definition */ #define NFI_CNFG (0x00) diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index 284fff62ac49..6b67b7dfe7ce 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -4502,11 +4502,13 @@ int nand_erase_nand(struct nand_chip *chip, struct erase_info *instr, len = instr->len; while (len) { + loff_t ofs = (loff_t)page << chip->page_shift; + /* Check if we have a bad block, we do not erase bad blocks! */ if (nand_block_checkbad(chip, ((loff_t) page) << chip->page_shift, allowbbt)) { - pr_warn("%s: attempt to erase a bad block at page 0x%08x\n", - __func__, page); + pr_warn("%s: attempt to erase a bad block at 0x%08llx\n", + __func__, (unsigned long long)ofs); ret = -EIO; goto erase_exit; } @@ -4524,8 +4526,7 @@ int nand_erase_nand(struct nand_chip *chip, struct erase_info *instr, if (ret) { pr_debug("%s: failed erase, page 0x%08x\n", __func__, page); - instr->fail_addr = - ((loff_t)page << chip->page_shift); + instr->fail_addr = ofs; goto erase_exit; } diff --git a/drivers/mtd/nand/raw/nand_ids.c b/drivers/mtd/nand/raw/nand_ids.c index 6e41902be35f..88c2440b47d8 100644 --- a/drivers/mtd/nand/raw/nand_ids.c +++ b/drivers/mtd/nand/raw/nand_ids.c @@ -29,6 +29,9 @@ struct nand_flash_dev nand_flash_ids[] = { {"TC58NVG0S3E 1G 3.3V 8-bit", { .id = {0x98, 0xd1, 0x90, 0x15, 0x76, 0x14, 0x01, 0x00} }, SZ_2K, SZ_128, SZ_128K, 0, 8, 64, NAND_ECC_INFO(1, SZ_512), }, + {"TC58NVG0S3HTA00 1G 3.3V 8-bit", + { .id = {0x98, 0xf1, 0x80, 0x15} }, + SZ_2K, SZ_128, SZ_128K, 0, 4, 128, NAND_ECC_INFO(8, SZ_512), }, {"TC58NVG2S0F 4G 3.3V 8-bit", { .id = {0x98, 0xdc, 0x90, 0x26, 0x76, 0x15, 0x01, 0x08} }, SZ_4K, SZ_512, SZ_256K, 0, 8, 224, NAND_ECC_INFO(4, SZ_512) }, @@ -58,6 +61,9 @@ struct nand_flash_dev nand_flash_ids[] = { {"TH58NVG2S3HBAI4 4G 3.3V 8-bit", { .id = {0x98, 0xdc, 0x91, 0x15, 0x76} }, SZ_2K, SZ_512, SZ_128K, 0, 5, 128, NAND_ECC_INFO(8, SZ_512) }, + {"TH58NVG3S0HBAI4 8G 3.3V 8-bit", + { .id = {0x98, 0xd3, 0x91, 0x26, 0x76} }, + SZ_4K, SZ_1K, SZ_256K, 0, 5, 256, NAND_ECC_INFO(8, SZ_512)}, LEGACY_ID_NAND("NAND 4MiB 5V 8-bit", 0x6B, 4, SZ_8K, SP_OPTIONS), LEGACY_ID_NAND("NAND 4MiB 3,3V 8-bit", 0xE3, 4, SZ_8K, SP_OPTIONS), diff --git a/drivers/mtd/nand/raw/nand_toshiba.c b/drivers/mtd/nand/raw/nand_toshiba.c index cf4f37959421..d3d34d71921f 100644 --- a/drivers/mtd/nand/raw/nand_toshiba.c +++ b/drivers/mtd/nand/raw/nand_toshiba.c @@ -287,8 +287,10 @@ static int toshiba_nand_init(struct nand_chip *chip) if (!strncmp("TC58NVG0S3E", chip->parameters.model, sizeof("TC58NVG0S3E") - 1)) tc58nvg0s3e_init(chip); - if (!strncmp("TH58NVG2S3HBAI4", chip->parameters.model, - sizeof("TH58NVG2S3HBAI4") - 1)) + if ((!strncmp("TH58NVG2S3HBAI4", chip->parameters.model, + sizeof("TH58NVG2S3HBAI4") - 1)) || + (!strncmp("TH58NVG3S0HBAI4", chip->parameters.model, + sizeof("TH58NVG3S0HBAI4") - 1))) th58nvg2s3hbai4_init(chip); return 0; diff --git a/drivers/mtd/nand/raw/omap_elm.c b/drivers/mtd/nand/raw/omap_elm.c index 893e9979c4a2..4796a48e1012 100644 --- a/drivers/mtd/nand/raw/omap_elm.c +++ b/drivers/mtd/nand/raw/omap_elm.c @@ -548,6 +548,7 @@ static SIMPLE_DEV_PM_OPS(elm_pm_ops, elm_suspend, elm_resume); #ifdef CONFIG_OF static const struct of_device_id elm_of_match[] = { { .compatible = "ti,am3352-elm" }, + { .compatible = "ti,am64-elm" }, {}, }; MODULE_DEVICE_TABLE(of, elm_of_match); diff --git a/drivers/mtd/nand/raw/renesas-nand-controller.c b/drivers/mtd/nand/raw/renesas-nand-controller.c index 6db063b230a9..1620e25a1147 100644 --- a/drivers/mtd/nand/raw/renesas-nand-controller.c +++ b/drivers/mtd/nand/raw/renesas-nand-controller.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #define COMMAND_REG 0x00 @@ -216,8 +217,7 @@ struct rnandc { struct nand_controller controller; struct device *dev; void __iomem *regs; - struct clk *hclk; - struct clk *eclk; + unsigned long ext_clk_rate; unsigned long assigned_cs; struct list_head chips; struct nand_chip *selected_chip; @@ -891,7 +891,7 @@ static int rnandc_setup_interface(struct nand_chip *chip, int chipnr, { struct rnand_chip *rnand = to_rnand(chip); struct rnandc *rnandc = to_rnandc(chip->controller); - unsigned int period_ns = 1000000000 / clk_get_rate(rnandc->eclk); + unsigned int period_ns = 1000000000 / rnandc->ext_clk_rate; const struct nand_sdr_timings *sdr; unsigned int cyc, cle, ale, bef_dly, ca_to_data; @@ -1319,6 +1319,7 @@ cleanup_chips: static int rnandc_probe(struct platform_device *pdev) { struct rnandc *rnandc; + struct clk *eclk; int irq, ret; rnandc = devm_kzalloc(&pdev->dev, sizeof(*rnandc), GFP_KERNEL); @@ -1335,29 +1336,26 @@ static int rnandc_probe(struct platform_device *pdev) if (IS_ERR(rnandc->regs)) return PTR_ERR(rnandc->regs); - /* APB clock */ - rnandc->hclk = devm_clk_get(&pdev->dev, "hclk"); - if (IS_ERR(rnandc->hclk)) - return PTR_ERR(rnandc->hclk); - - /* External NAND bus clock */ - rnandc->eclk = devm_clk_get(&pdev->dev, "eclk"); - if (IS_ERR(rnandc->eclk)) - return PTR_ERR(rnandc->eclk); - - ret = clk_prepare_enable(rnandc->hclk); - if (ret) + devm_pm_runtime_enable(&pdev->dev); + ret = pm_runtime_resume_and_get(&pdev->dev); + if (ret < 0) return ret; - ret = clk_prepare_enable(rnandc->eclk); - if (ret) - goto disable_hclk; + /* The external NAND bus clock rate is needed for computing timings */ + eclk = clk_get(&pdev->dev, "eclk"); + if (IS_ERR(eclk)) { + ret = PTR_ERR(eclk); + goto dis_runtime_pm; + } + + rnandc->ext_clk_rate = clk_get_rate(eclk); + clk_put(eclk); rnandc_dis_interrupts(rnandc); irq = platform_get_irq_optional(pdev, 0); if (irq == -EPROBE_DEFER) { ret = irq; - goto disable_eclk; + goto dis_runtime_pm; } else if (irq < 0) { dev_info(&pdev->dev, "No IRQ found, fallback to polling\n"); rnandc->use_polling = true; @@ -1365,12 +1363,12 @@ static int rnandc_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, irq, rnandc_irq_handler, 0, "renesas-nand-controller", rnandc); if (ret < 0) - goto disable_eclk; + goto dis_runtime_pm; } ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); if (ret) - goto disable_eclk; + goto dis_runtime_pm; rnandc_clear_fifo(rnandc); @@ -1378,14 +1376,12 @@ static int rnandc_probe(struct platform_device *pdev) ret = rnandc_chips_init(rnandc); if (ret) - goto disable_eclk; + goto dis_runtime_pm; return 0; -disable_eclk: - clk_disable_unprepare(rnandc->eclk); -disable_hclk: - clk_disable_unprepare(rnandc->hclk); +dis_runtime_pm: + pm_runtime_put(&pdev->dev); return ret; } @@ -1396,8 +1392,7 @@ static int rnandc_remove(struct platform_device *pdev) rnandc_chips_cleanup(rnandc); - clk_disable_unprepare(rnandc->eclk); - clk_disable_unprepare(rnandc->hclk); + pm_runtime_put(&pdev->dev); return 0; } diff --git a/drivers/mtd/nand/raw/rockchip-nand-controller.c b/drivers/mtd/nand/raw/rockchip-nand-controller.c index cbaa4f1c83da..f133985cc053 100644 --- a/drivers/mtd/nand/raw/rockchip-nand-controller.c +++ b/drivers/mtd/nand/raw/rockchip-nand-controller.c @@ -911,8 +911,7 @@ static int rk_nfc_enable_clks(struct device *dev, struct rk_nfc *nfc) ret = clk_prepare_enable(nfc->ahb_clk); if (ret) { dev_err(dev, "failed to enable ahb clk\n"); - if (!IS_ERR(nfc->nfc_clk)) - clk_disable_unprepare(nfc->nfc_clk); + clk_disable_unprepare(nfc->nfc_clk); return ret; } @@ -921,8 +920,7 @@ static int rk_nfc_enable_clks(struct device *dev, struct rk_nfc *nfc) static void rk_nfc_disable_clks(struct rk_nfc *nfc) { - if (!IS_ERR(nfc->nfc_clk)) - clk_disable_unprepare(nfc->nfc_clk); + clk_disable_unprepare(nfc->nfc_clk); clk_disable_unprepare(nfc->ahb_clk); } diff --git a/drivers/mtd/nand/raw/tmio_nand.c b/drivers/mtd/nand/raw/tmio_nand.c index de8e919d0ebe..8f1a42bf199c 100644 --- a/drivers/mtd/nand/raw/tmio_nand.c +++ b/drivers/mtd/nand/raw/tmio_nand.c @@ -390,6 +390,9 @@ static int tmio_probe(struct platform_device *dev) if (data == NULL) dev_warn(&dev->dev, "NULL platform data!\n"); + if (!ccr || !fcr) + return -EINVAL; + tmio = devm_kzalloc(&dev->dev, sizeof(*tmio), GFP_KERNEL); if (!tmio) return -ENOMEM; diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile index 9662b9c1d5a9..80dabe6ff0f3 100644 --- a/drivers/mtd/nand/spi/Makefile +++ b/drivers/mtd/nand/spi/Makefile @@ -1,3 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 -spinand-objs := core.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o +spinand-objs := core.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o xtx.o obj-$(CONFIG_MTD_SPI_NAND) += spinand.o diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index ff8336870bc0..d5b685d1605e 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -933,6 +933,7 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = { ¶gon_spinand_manufacturer, &toshiba_spinand_manufacturer, &winbond_spinand_manufacturer, + &xtx_spinand_manufacturer, }; static int spinand_manufacturer_match(struct spinand_device *spinand, diff --git a/drivers/mtd/nand/spi/gigadevice.c b/drivers/mtd/nand/spi/gigadevice.c index 1dd1c5898093..6b043e24855f 100644 --- a/drivers/mtd/nand/spi/gigadevice.c +++ b/drivers/mtd/nand/spi/gigadevice.c @@ -39,6 +39,22 @@ static SPINAND_OP_VARIANTS(read_cache_variants_f, SPINAND_PAGE_READ_FROM_CACHE_OP_3A(true, 0, 1, NULL, 0), SPINAND_PAGE_READ_FROM_CACHE_OP_3A(false, 0, 0, NULL, 0)); +static SPINAND_OP_VARIANTS(read_cache_variants_1gq5, + SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + +static SPINAND_OP_VARIANTS(read_cache_variants_2gq5, + SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 4, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 2, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + static SPINAND_OP_VARIANTS(write_cache_variants, SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), SPINAND_PROG_LOAD(true, 0, NULL, 0)); @@ -325,6 +341,36 @@ static const struct spinand_info gigadevice_spinand_table[] = { SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GQ4RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xc1), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GQ4UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xd2), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GQ4RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xc2), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), SPINAND_INFO("GD5F1GQ4UFxxG", SPINAND_ID(SPINAND_READID_METHOD_OPCODE, 0xb1, 0x48), NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), @@ -339,12 +385,122 @@ static const struct spinand_info gigadevice_spinand_table[] = { SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x51), NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), NAND_ECCREQ(4, 512), - SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, &write_cache_variants, &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GQ5RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x41), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GQ5UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x52), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GQ5RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x42), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F4GQ6UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x55), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 2, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F4GQ6RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x45), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 2, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GM7UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x91), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GM7RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x81), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GM7UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x92), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GM7RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x82), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F4GM8UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x95), + NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F4GM8RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x85), + NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), }; static const struct spinand_manufacturer_ops gigadevice_spinand_manuf_ops = { diff --git a/drivers/mtd/nand/spi/xtx.c b/drivers/mtd/nand/spi/xtx.c new file mode 100644 index 000000000000..3911520f718c --- /dev/null +++ b/drivers/mtd/nand/spi/xtx.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Author: + * Felix Matouschek + */ + +#include +#include +#include + +#define SPINAND_MFR_XTX 0x0B + +#define XT26G0XA_STATUS_ECC_MASK GENMASK(5, 2) +#define XT26G0XA_STATUS_ECC_NO_DETECTED (0 << 2) +#define XT26G0XA_STATUS_ECC_8_CORRECTED (3 << 4) +#define XT26G0XA_STATUS_ECC_UNCOR_ERROR (2 << 4) + +static SPINAND_OP_VARIANTS(read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + +static SPINAND_OP_VARIANTS(write_cache_variants, + SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), + SPINAND_PROG_LOAD(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(update_cache_variants, + SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), + SPINAND_PROG_LOAD(false, 0, NULL, 0)); + +static int xt26g0xa_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = 48; + region->length = 16; + + return 0; +} + +static int xt26g0xa_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = 1; + region->length = 47; + + return 0; +} + +static const struct mtd_ooblayout_ops xt26g0xa_ooblayout = { + .ecc = xt26g0xa_ooblayout_ecc, + .free = xt26g0xa_ooblayout_free, +}; + +static int xt26g0xa_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + status = status & XT26G0XA_STATUS_ECC_MASK; + + switch (status) { + case XT26G0XA_STATUS_ECC_NO_DETECTED: + return 0; + case XT26G0XA_STATUS_ECC_8_CORRECTED: + return 8; + case XT26G0XA_STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + default: + break; + } + + /* At this point values greater than (2 << 4) are invalid */ + if (status > XT26G0XA_STATUS_ECC_UNCOR_ERROR) + return -EINVAL; + + /* (1 << 2) through (7 << 2) are 1-7 corrected errors */ + return status >> 2; +} + +static const struct spinand_info xtx_spinand_table[] = { + SPINAND_INFO("XT26G01A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE1), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&xt26g0xa_ooblayout, + xt26g0xa_ecc_get_status)), + SPINAND_INFO("XT26G02A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE2), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&xt26g0xa_ooblayout, + xt26g0xa_ecc_get_status)), + SPINAND_INFO("XT26G04A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE3), + NAND_MEMORG(1, 2048, 64, 128, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&xt26g0xa_ooblayout, + xt26g0xa_ecc_get_status)), +}; + +static const struct spinand_manufacturer_ops xtx_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer xtx_spinand_manufacturer = { + .id = SPINAND_MFR_XTX, + .name = "XTX", + .chips = xtx_spinand_table, + .nchips = ARRAY_SIZE(xtx_spinand_table), + .ops = &xtx_spinand_manuf_ops, +}; diff --git a/drivers/mtd/parsers/bcm47xxpart.c b/drivers/mtd/parsers/bcm47xxpart.c index 6012a10f10c8..50fcf4c2174b 100644 --- a/drivers/mtd/parsers/bcm47xxpart.c +++ b/drivers/mtd/parsers/bcm47xxpart.c @@ -237,7 +237,7 @@ static int bcm47xxpart_parse(struct mtd_info *master, (uint8_t *)buf); if (err && !mtd_is_bitflip(err)) { pr_err("mtd_read error while parsing (offset: 0x%X): %d\n", - offset, err); + offset + 0x8000, err); continue; } diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile index 6b904e439372..e347b435a038 100644 --- a/drivers/mtd/spi-nor/Makefile +++ b/drivers/mtd/spi-nor/Makefile @@ -17,6 +17,7 @@ spi-nor-objs += sst.o spi-nor-objs += winbond.o spi-nor-objs += xilinx.o spi-nor-objs += xmc.o +spi-nor-$(CONFIG_DEBUG_FS) += debugfs.o obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o obj-$(CONFIG_MTD_SPI_NOR) += controllers/ diff --git a/drivers/mtd/spi-nor/controllers/Kconfig b/drivers/mtd/spi-nor/controllers/Kconfig index 50f4f3484d42..ca45dcd3ffe8 100644 --- a/drivers/mtd/spi-nor/controllers/Kconfig +++ b/drivers/mtd/spi-nor/controllers/Kconfig @@ -1,14 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only -config SPI_ASPEED_SMC - tristate "Aspeed flash controllers in SPI mode" - depends on ARCH_ASPEED || COMPILE_TEST - depends on HAS_IOMEM && OF - help - This enables support for the Firmware Memory controller (FMC) - in the Aspeed AST2500/AST2400 SoCs when attached to SPI NOR chips, - and support for the SPI flash memory controller (SPI) for - the host firmware. The implementation only supports SPI NOR. - config SPI_HISI_SFC tristate "Hisilicon FMC SPI NOR Flash Controller(SFC)" depends on ARCH_HISI || COMPILE_TEST diff --git a/drivers/mtd/spi-nor/controllers/Makefile b/drivers/mtd/spi-nor/controllers/Makefile index 6e2a1dc68466..0b8e1d530913 100644 --- a/drivers/mtd/spi-nor/controllers/Makefile +++ b/drivers/mtd/spi-nor/controllers/Makefile @@ -1,4 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 -obj-$(CONFIG_SPI_ASPEED_SMC) += aspeed-smc.o obj-$(CONFIG_SPI_HISI_SFC) += hisi-sfc.o obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o diff --git a/drivers/mtd/spi-nor/controllers/aspeed-smc.c b/drivers/mtd/spi-nor/controllers/aspeed-smc.c deleted file mode 100644 index acfe010f9dd7..000000000000 --- a/drivers/mtd/spi-nor/controllers/aspeed-smc.c +++ /dev/null @@ -1,921 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * ASPEED Static Memory Controller driver - * - * Copyright (c) 2015-2016, IBM Corporation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DEVICE_NAME "aspeed-smc" - -/* - * The driver only support SPI flash - */ -enum aspeed_smc_flash_type { - smc_type_nor = 0, - smc_type_nand = 1, - smc_type_spi = 2, -}; - -struct aspeed_smc_chip; - -struct aspeed_smc_info { - u32 maxsize; /* maximum size of chip window */ - u8 nce; /* number of chip enables */ - bool hastype; /* flash type field exists in config reg */ - u8 we0; /* shift for write enable bit for CE0 */ - u8 ctl0; /* offset in regs of ctl for CE0 */ - - void (*set_4b)(struct aspeed_smc_chip *chip); -}; - -static void aspeed_smc_chip_set_4b_spi_2400(struct aspeed_smc_chip *chip); -static void aspeed_smc_chip_set_4b(struct aspeed_smc_chip *chip); - -static const struct aspeed_smc_info fmc_2400_info = { - .maxsize = 64 * 1024 * 1024, - .nce = 5, - .hastype = true, - .we0 = 16, - .ctl0 = 0x10, - .set_4b = aspeed_smc_chip_set_4b, -}; - -static const struct aspeed_smc_info spi_2400_info = { - .maxsize = 64 * 1024 * 1024, - .nce = 1, - .hastype = false, - .we0 = 0, - .ctl0 = 0x04, - .set_4b = aspeed_smc_chip_set_4b_spi_2400, -}; - -static const struct aspeed_smc_info fmc_2500_info = { - .maxsize = 256 * 1024 * 1024, - .nce = 3, - .hastype = true, - .we0 = 16, - .ctl0 = 0x10, - .set_4b = aspeed_smc_chip_set_4b, -}; - -static const struct aspeed_smc_info spi_2500_info = { - .maxsize = 128 * 1024 * 1024, - .nce = 2, - .hastype = false, - .we0 = 16, - .ctl0 = 0x10, - .set_4b = aspeed_smc_chip_set_4b, -}; - -enum aspeed_smc_ctl_reg_value { - smc_base, /* base value without mode for other commands */ - smc_read, /* command reg for (maybe fast) reads */ - smc_write, /* command reg for writes */ - smc_max, -}; - -struct aspeed_smc_controller; - -struct aspeed_smc_chip { - int cs; - struct aspeed_smc_controller *controller; - void __iomem *ctl; /* control register */ - void __iomem *ahb_base; /* base of chip window */ - u32 ahb_window_size; /* chip mapping window size */ - u32 ctl_val[smc_max]; /* control settings */ - enum aspeed_smc_flash_type type; /* what type of flash */ - struct spi_nor nor; -}; - -struct aspeed_smc_controller { - struct device *dev; - - struct mutex mutex; /* controller access mutex */ - const struct aspeed_smc_info *info; /* type info of controller */ - void __iomem *regs; /* controller registers */ - void __iomem *ahb_base; /* per-chip windows resource */ - u32 ahb_window_size; /* full mapping window size */ - - struct aspeed_smc_chip *chips[]; /* pointers to attached chips */ -}; - -/* - * SPI Flash Configuration Register (AST2500 SPI) - * or - * Type setting Register (AST2500 FMC). - * CE0 and CE1 can only be of type SPI. CE2 can be of type NOR but the - * driver does not support it. - */ -#define CONFIG_REG 0x0 -#define CONFIG_DISABLE_LEGACY BIT(31) /* 1 */ - -#define CONFIG_CE2_WRITE BIT(18) -#define CONFIG_CE1_WRITE BIT(17) -#define CONFIG_CE0_WRITE BIT(16) - -#define CONFIG_CE2_TYPE BIT(4) /* AST2500 FMC only */ -#define CONFIG_CE1_TYPE BIT(2) /* AST2500 FMC only */ -#define CONFIG_CE0_TYPE BIT(0) /* AST2500 FMC only */ - -/* - * CE Control Register - */ -#define CE_CONTROL_REG 0x4 - -/* - * CEx Control Register - */ -#define CONTROL_AAF_MODE BIT(31) -#define CONTROL_IO_MODE_MASK GENMASK(30, 28) -#define CONTROL_IO_DUAL_DATA BIT(29) -#define CONTROL_IO_DUAL_ADDR_DATA (BIT(29) | BIT(28)) -#define CONTROL_IO_QUAD_DATA BIT(30) -#define CONTROL_IO_QUAD_ADDR_DATA (BIT(30) | BIT(28)) -#define CONTROL_CE_INACTIVE_SHIFT 24 -#define CONTROL_CE_INACTIVE_MASK GENMASK(27, \ - CONTROL_CE_INACTIVE_SHIFT) -/* 0 = 16T ... 15 = 1T T=HCLK */ -#define CONTROL_COMMAND_SHIFT 16 -#define CONTROL_DUMMY_COMMAND_OUT BIT(15) -#define CONTROL_IO_DUMMY_HI BIT(14) -#define CONTROL_IO_DUMMY_HI_SHIFT 14 -#define CONTROL_CLK_DIV4 BIT(13) /* others */ -#define CONTROL_IO_ADDRESS_4B BIT(13) /* AST2400 SPI */ -#define CONTROL_RW_MERGE BIT(12) -#define CONTROL_IO_DUMMY_LO_SHIFT 6 -#define CONTROL_IO_DUMMY_LO GENMASK(7, \ - CONTROL_IO_DUMMY_LO_SHIFT) -#define CONTROL_IO_DUMMY_MASK (CONTROL_IO_DUMMY_HI | \ - CONTROL_IO_DUMMY_LO) -#define CONTROL_IO_DUMMY_SET(dummy) \ - (((((dummy) >> 2) & 0x1) << CONTROL_IO_DUMMY_HI_SHIFT) | \ - (((dummy) & 0x3) << CONTROL_IO_DUMMY_LO_SHIFT)) - -#define CONTROL_CLOCK_FREQ_SEL_SHIFT 8 -#define CONTROL_CLOCK_FREQ_SEL_MASK GENMASK(11, \ - CONTROL_CLOCK_FREQ_SEL_SHIFT) -#define CONTROL_LSB_FIRST BIT(5) -#define CONTROL_CLOCK_MODE_3 BIT(4) -#define CONTROL_IN_DUAL_DATA BIT(3) -#define CONTROL_CE_STOP_ACTIVE_CONTROL BIT(2) -#define CONTROL_COMMAND_MODE_MASK GENMASK(1, 0) -#define CONTROL_COMMAND_MODE_NORMAL 0 -#define CONTROL_COMMAND_MODE_FREAD 1 -#define CONTROL_COMMAND_MODE_WRITE 2 -#define CONTROL_COMMAND_MODE_USER 3 - -#define CONTROL_KEEP_MASK \ - (CONTROL_AAF_MODE | CONTROL_CE_INACTIVE_MASK | CONTROL_CLK_DIV4 | \ - CONTROL_CLOCK_FREQ_SEL_MASK | CONTROL_LSB_FIRST | CONTROL_CLOCK_MODE_3) - -/* - * The Segment Register uses a 8MB unit to encode the start address - * and the end address of the mapping window of a flash SPI slave : - * - * | byte 1 | byte 2 | byte 3 | byte 4 | - * +--------+--------+--------+--------+ - * | end | start | 0 | 0 | - */ -#define SEGMENT_ADDR_REG0 0x30 -#define SEGMENT_ADDR_START(_r) ((((_r) >> 16) & 0xFF) << 23) -#define SEGMENT_ADDR_END(_r) ((((_r) >> 24) & 0xFF) << 23) -#define SEGMENT_ADDR_VALUE(start, end) \ - (((((start) >> 23) & 0xFF) << 16) | ((((end) >> 23) & 0xFF) << 24)) -#define SEGMENT_ADDR_REG(controller, cs) \ - ((controller)->regs + SEGMENT_ADDR_REG0 + (cs) * 4) - -/* - * In user mode all data bytes read or written to the chip decode address - * range are transferred to or from the SPI bus. The range is treated as a - * fifo of arbitratry 1, 2, or 4 byte width but each write has to be aligned - * to its size. The address within the multiple 8kB range is ignored when - * sending bytes to the SPI bus. - * - * On the arm architecture, as of Linux version 4.3, memcpy_fromio and - * memcpy_toio on little endian targets use the optimized memcpy routines - * that were designed for well behavied memory storage. These routines - * have a stutter if the source and destination are not both word aligned, - * once with a duplicate access to the source after aligning to the - * destination to a word boundary, and again with a duplicate access to - * the source when the final byte count is not word aligned. - * - * When writing or reading the fifo this stutter discards data or sends - * too much data to the fifo and can not be used by this driver. - * - * While the low level io string routines that implement the insl family do - * the desired accesses and memory increments, the cross architecture io - * macros make them essentially impossible to use on a memory mapped address - * instead of a a token from the call to iomap of an io port. - * - * These fifo routines use readl and friends to a constant io port and update - * the memory buffer pointer and count via explicit code. The final updates - * to len are optimistically suppressed. - */ -static int aspeed_smc_read_from_ahb(void *buf, void __iomem *src, size_t len) -{ - size_t offset = 0; - - if (IS_ALIGNED((uintptr_t)src, sizeof(uintptr_t)) && - IS_ALIGNED((uintptr_t)buf, sizeof(uintptr_t))) { - ioread32_rep(src, buf, len >> 2); - offset = len & ~0x3; - len -= offset; - } - ioread8_rep(src, (u8 *)buf + offset, len); - return 0; -} - -static int aspeed_smc_write_to_ahb(void __iomem *dst, const void *buf, - size_t len) -{ - size_t offset = 0; - - if (IS_ALIGNED((uintptr_t)dst, sizeof(uintptr_t)) && - IS_ALIGNED((uintptr_t)buf, sizeof(uintptr_t))) { - iowrite32_rep(dst, buf, len >> 2); - offset = len & ~0x3; - len -= offset; - } - iowrite8_rep(dst, (const u8 *)buf + offset, len); - return 0; -} - -static inline u32 aspeed_smc_chip_write_bit(struct aspeed_smc_chip *chip) -{ - return BIT(chip->controller->info->we0 + chip->cs); -} - -static void aspeed_smc_chip_check_config(struct aspeed_smc_chip *chip) -{ - struct aspeed_smc_controller *controller = chip->controller; - u32 reg; - - reg = readl(controller->regs + CONFIG_REG); - - if (reg & aspeed_smc_chip_write_bit(chip)) - return; - - dev_dbg(controller->dev, "config write is not set ! @%p: 0x%08x\n", - controller->regs + CONFIG_REG, reg); - reg |= aspeed_smc_chip_write_bit(chip); - writel(reg, controller->regs + CONFIG_REG); -} - -static void aspeed_smc_start_user(struct spi_nor *nor) -{ - struct aspeed_smc_chip *chip = nor->priv; - u32 ctl = chip->ctl_val[smc_base]; - - /* - * When the chip is controlled in user mode, we need write - * access to send the opcodes to it. So check the config. - */ - aspeed_smc_chip_check_config(chip); - - ctl |= CONTROL_COMMAND_MODE_USER | - CONTROL_CE_STOP_ACTIVE_CONTROL; - writel(ctl, chip->ctl); - - ctl &= ~CONTROL_CE_STOP_ACTIVE_CONTROL; - writel(ctl, chip->ctl); -} - -static void aspeed_smc_stop_user(struct spi_nor *nor) -{ - struct aspeed_smc_chip *chip = nor->priv; - - u32 ctl = chip->ctl_val[smc_read]; - u32 ctl2 = ctl | CONTROL_COMMAND_MODE_USER | - CONTROL_CE_STOP_ACTIVE_CONTROL; - - writel(ctl2, chip->ctl); /* stop user CE control */ - writel(ctl, chip->ctl); /* default to fread or read mode */ -} - -static int aspeed_smc_prep(struct spi_nor *nor) -{ - struct aspeed_smc_chip *chip = nor->priv; - - mutex_lock(&chip->controller->mutex); - return 0; -} - -static void aspeed_smc_unprep(struct spi_nor *nor) -{ - struct aspeed_smc_chip *chip = nor->priv; - - mutex_unlock(&chip->controller->mutex); -} - -static int aspeed_smc_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, - size_t len) -{ - struct aspeed_smc_chip *chip = nor->priv; - - aspeed_smc_start_user(nor); - aspeed_smc_write_to_ahb(chip->ahb_base, &opcode, 1); - aspeed_smc_read_from_ahb(buf, chip->ahb_base, len); - aspeed_smc_stop_user(nor); - return 0; -} - -static int aspeed_smc_write_reg(struct spi_nor *nor, u8 opcode, const u8 *buf, - size_t len) -{ - struct aspeed_smc_chip *chip = nor->priv; - - aspeed_smc_start_user(nor); - aspeed_smc_write_to_ahb(chip->ahb_base, &opcode, 1); - aspeed_smc_write_to_ahb(chip->ahb_base, buf, len); - aspeed_smc_stop_user(nor); - return 0; -} - -static void aspeed_smc_send_cmd_addr(struct spi_nor *nor, u8 cmd, u32 addr) -{ - struct aspeed_smc_chip *chip = nor->priv; - __be32 temp; - u32 cmdaddr; - - switch (nor->addr_width) { - default: - WARN_ONCE(1, "Unexpected address width %u, defaulting to 3\n", - nor->addr_width); - fallthrough; - case 3: - cmdaddr = addr & 0xFFFFFF; - cmdaddr |= cmd << 24; - - temp = cpu_to_be32(cmdaddr); - aspeed_smc_write_to_ahb(chip->ahb_base, &temp, 4); - break; - case 4: - temp = cpu_to_be32(addr); - aspeed_smc_write_to_ahb(chip->ahb_base, &cmd, 1); - aspeed_smc_write_to_ahb(chip->ahb_base, &temp, 4); - break; - } -} - -static ssize_t aspeed_smc_read_user(struct spi_nor *nor, loff_t from, - size_t len, u_char *read_buf) -{ - struct aspeed_smc_chip *chip = nor->priv; - int i; - u8 dummy = 0xFF; - - aspeed_smc_start_user(nor); - aspeed_smc_send_cmd_addr(nor, nor->read_opcode, from); - for (i = 0; i < chip->nor.read_dummy / 8; i++) - aspeed_smc_write_to_ahb(chip->ahb_base, &dummy, sizeof(dummy)); - - aspeed_smc_read_from_ahb(read_buf, chip->ahb_base, len); - aspeed_smc_stop_user(nor); - return len; -} - -static ssize_t aspeed_smc_write_user(struct spi_nor *nor, loff_t to, - size_t len, const u_char *write_buf) -{ - struct aspeed_smc_chip *chip = nor->priv; - - aspeed_smc_start_user(nor); - aspeed_smc_send_cmd_addr(nor, nor->program_opcode, to); - aspeed_smc_write_to_ahb(chip->ahb_base, write_buf, len); - aspeed_smc_stop_user(nor); - return len; -} - -static int aspeed_smc_unregister(struct aspeed_smc_controller *controller) -{ - struct aspeed_smc_chip *chip; - int n; - - for (n = 0; n < controller->info->nce; n++) { - chip = controller->chips[n]; - if (chip) - mtd_device_unregister(&chip->nor.mtd); - } - - return 0; -} - -static int aspeed_smc_remove(struct platform_device *dev) -{ - return aspeed_smc_unregister(platform_get_drvdata(dev)); -} - -static const struct of_device_id aspeed_smc_matches[] = { - { .compatible = "aspeed,ast2400-fmc", .data = &fmc_2400_info }, - { .compatible = "aspeed,ast2400-spi", .data = &spi_2400_info }, - { .compatible = "aspeed,ast2500-fmc", .data = &fmc_2500_info }, - { .compatible = "aspeed,ast2500-spi", .data = &spi_2500_info }, - { } -}; -MODULE_DEVICE_TABLE(of, aspeed_smc_matches); - -/* - * Each chip has a mapping window defined by a segment address - * register defining a start and an end address on the AHB bus. These - * addresses can be configured to fit the chip size and offer a - * contiguous memory region across chips. For the moment, we only - * check that each chip segment is valid. - */ -static void __iomem *aspeed_smc_chip_base(struct aspeed_smc_chip *chip, - struct resource *res) -{ - struct aspeed_smc_controller *controller = chip->controller; - u32 offset = 0; - u32 reg; - - if (controller->info->nce > 1) { - reg = readl(SEGMENT_ADDR_REG(controller, chip->cs)); - - if (SEGMENT_ADDR_START(reg) >= SEGMENT_ADDR_END(reg)) - return NULL; - - offset = SEGMENT_ADDR_START(reg) - res->start; - } - - return controller->ahb_base + offset; -} - -static u32 aspeed_smc_ahb_base_phy(struct aspeed_smc_controller *controller) -{ - u32 seg0_val = readl(SEGMENT_ADDR_REG(controller, 0)); - - return SEGMENT_ADDR_START(seg0_val); -} - -static u32 chip_set_segment(struct aspeed_smc_chip *chip, u32 cs, u32 start, - u32 size) -{ - struct aspeed_smc_controller *controller = chip->controller; - void __iomem *seg_reg; - u32 seg_oldval, seg_newval, ahb_base_phy, end; - - ahb_base_phy = aspeed_smc_ahb_base_phy(controller); - - seg_reg = SEGMENT_ADDR_REG(controller, cs); - seg_oldval = readl(seg_reg); - - /* - * If the chip size is not specified, use the default segment - * size, but take into account the possible overlap with the - * previous segment - */ - if (!size) - size = SEGMENT_ADDR_END(seg_oldval) - start; - - /* - * The segment cannot exceed the maximum window size of the - * controller. - */ - if (start + size > ahb_base_phy + controller->ahb_window_size) { - size = ahb_base_phy + controller->ahb_window_size - start; - dev_warn(chip->nor.dev, "CE%d window resized to %dMB", - cs, size >> 20); - } - - end = start + size; - seg_newval = SEGMENT_ADDR_VALUE(start, end); - writel(seg_newval, seg_reg); - - /* - * Restore default value if something goes wrong. The chip - * might have set some bogus value and we would loose access - * to the chip. - */ - if (seg_newval != readl(seg_reg)) { - dev_err(chip->nor.dev, "CE%d window invalid", cs); - writel(seg_oldval, seg_reg); - start = SEGMENT_ADDR_START(seg_oldval); - end = SEGMENT_ADDR_END(seg_oldval); - size = end - start; - } - - dev_info(chip->nor.dev, "CE%d window [ 0x%.8x - 0x%.8x ] %dMB", - cs, start, end, size >> 20); - - return size; -} - -/* - * The segment register defines the mapping window on the AHB bus and - * it needs to be configured depending on the chip size. The segment - * register of the following CE also needs to be tuned in order to - * provide a contiguous window across multiple chips. - * - * This is expected to be called in increasing CE order - */ -static u32 aspeed_smc_chip_set_segment(struct aspeed_smc_chip *chip) -{ - struct aspeed_smc_controller *controller = chip->controller; - u32 ahb_base_phy, start; - u32 size = chip->nor.mtd.size; - - /* - * Each controller has a chip size limit for direct memory - * access - */ - if (size > controller->info->maxsize) - size = controller->info->maxsize; - - /* - * The AST2400 SPI controller only handles one chip and does - * not have segment registers. Let's use the chip size for the - * AHB window. - */ - if (controller->info == &spi_2400_info) - goto out; - - /* - * The AST2500 SPI controller has a HW bug when the CE0 chip - * size reaches 128MB. Enforce a size limit of 120MB to - * prevent the controller from using bogus settings in the - * segment register. - */ - if (chip->cs == 0 && controller->info == &spi_2500_info && - size == SZ_128M) { - size = 120 << 20; - dev_info(chip->nor.dev, - "CE%d window resized to %dMB (AST2500 HW quirk)", - chip->cs, size >> 20); - } - - ahb_base_phy = aspeed_smc_ahb_base_phy(controller); - - /* - * As a start address for the current segment, use the default - * start address if we are handling CE0 or use the previous - * segment ending address - */ - if (chip->cs) { - u32 prev = readl(SEGMENT_ADDR_REG(controller, chip->cs - 1)); - - start = SEGMENT_ADDR_END(prev); - } else { - start = ahb_base_phy; - } - - size = chip_set_segment(chip, chip->cs, start, size); - - /* Update chip base address on the AHB bus */ - chip->ahb_base = controller->ahb_base + (start - ahb_base_phy); - - /* - * Now, make sure the next segment does not overlap with the - * current one we just configured, even if there is no - * available chip. That could break access in Command Mode. - */ - if (chip->cs < controller->info->nce - 1) - chip_set_segment(chip, chip->cs + 1, start + size, 0); - -out: - if (size < chip->nor.mtd.size) - dev_warn(chip->nor.dev, - "CE%d window too small for chip %dMB", - chip->cs, (u32)chip->nor.mtd.size >> 20); - - return size; -} - -static void aspeed_smc_chip_enable_write(struct aspeed_smc_chip *chip) -{ - struct aspeed_smc_controller *controller = chip->controller; - u32 reg; - - reg = readl(controller->regs + CONFIG_REG); - - reg |= aspeed_smc_chip_write_bit(chip); - writel(reg, controller->regs + CONFIG_REG); -} - -static void aspeed_smc_chip_set_type(struct aspeed_smc_chip *chip, int type) -{ - struct aspeed_smc_controller *controller = chip->controller; - u32 reg; - - chip->type = type; - - reg = readl(controller->regs + CONFIG_REG); - reg &= ~(3 << (chip->cs * 2)); - reg |= chip->type << (chip->cs * 2); - writel(reg, controller->regs + CONFIG_REG); -} - -/* - * The first chip of the AST2500 FMC flash controller is strapped by - * hardware, or autodetected, but other chips need to be set. Enforce - * the 4B setting for all chips. - */ -static void aspeed_smc_chip_set_4b(struct aspeed_smc_chip *chip) -{ - struct aspeed_smc_controller *controller = chip->controller; - u32 reg; - - reg = readl(controller->regs + CE_CONTROL_REG); - reg |= 1 << chip->cs; - writel(reg, controller->regs + CE_CONTROL_REG); -} - -/* - * The AST2400 SPI flash controller does not have a CE Control - * register. It uses the CE0 control register to set 4Byte mode at the - * controller level. - */ -static void aspeed_smc_chip_set_4b_spi_2400(struct aspeed_smc_chip *chip) -{ - chip->ctl_val[smc_base] |= CONTROL_IO_ADDRESS_4B; - chip->ctl_val[smc_read] |= CONTROL_IO_ADDRESS_4B; -} - -static int aspeed_smc_chip_setup_init(struct aspeed_smc_chip *chip, - struct resource *res) -{ - struct aspeed_smc_controller *controller = chip->controller; - const struct aspeed_smc_info *info = controller->info; - u32 reg, base_reg; - - /* - * Always turn on the write enable bit to allow opcodes to be - * sent in user mode. - */ - aspeed_smc_chip_enable_write(chip); - - /* The driver only supports SPI type flash */ - if (info->hastype) - aspeed_smc_chip_set_type(chip, smc_type_spi); - - /* - * Configure chip base address in memory - */ - chip->ahb_base = aspeed_smc_chip_base(chip, res); - if (!chip->ahb_base) { - dev_warn(chip->nor.dev, "CE%d window closed", chip->cs); - return -EINVAL; - } - - /* - * Get value of the inherited control register. U-Boot usually - * does some timing calibration on the FMC chip, so it's good - * to keep them. In the future, we should handle calibration - * from Linux. - */ - reg = readl(chip->ctl); - dev_dbg(controller->dev, "control register: %08x\n", reg); - - base_reg = reg & CONTROL_KEEP_MASK; - if (base_reg != reg) { - dev_dbg(controller->dev, - "control register changed to: %08x\n", - base_reg); - } - chip->ctl_val[smc_base] = base_reg; - - /* - * Retain the prior value of the control register as the - * default if it was normal access mode. Otherwise start with - * the sanitized base value set to read mode. - */ - if ((reg & CONTROL_COMMAND_MODE_MASK) == - CONTROL_COMMAND_MODE_NORMAL) - chip->ctl_val[smc_read] = reg; - else - chip->ctl_val[smc_read] = chip->ctl_val[smc_base] | - CONTROL_COMMAND_MODE_NORMAL; - - dev_dbg(controller->dev, "default control register: %08x\n", - chip->ctl_val[smc_read]); - return 0; -} - -static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip) -{ - struct aspeed_smc_controller *controller = chip->controller; - const struct aspeed_smc_info *info = controller->info; - u32 cmd; - - if (chip->nor.addr_width == 4 && info->set_4b) - info->set_4b(chip); - - /* This is for direct AHB access when using Command Mode. */ - chip->ahb_window_size = aspeed_smc_chip_set_segment(chip); - - /* - * base mode has not been optimized yet. use it for writes. - */ - chip->ctl_val[smc_write] = chip->ctl_val[smc_base] | - chip->nor.program_opcode << CONTROL_COMMAND_SHIFT | - CONTROL_COMMAND_MODE_WRITE; - - dev_dbg(controller->dev, "write control register: %08x\n", - chip->ctl_val[smc_write]); - - /* - * TODO: Adjust clocks if fast read is supported and interpret - * SPI NOR flags to adjust controller settings. - */ - if (chip->nor.read_proto == SNOR_PROTO_1_1_1) { - if (chip->nor.read_dummy == 0) - cmd = CONTROL_COMMAND_MODE_NORMAL; - else - cmd = CONTROL_COMMAND_MODE_FREAD; - } else { - dev_err(chip->nor.dev, "unsupported SPI read mode\n"); - return -EINVAL; - } - - chip->ctl_val[smc_read] |= cmd | - CONTROL_IO_DUMMY_SET(chip->nor.read_dummy / 8); - - dev_dbg(controller->dev, "base control register: %08x\n", - chip->ctl_val[smc_read]); - return 0; -} - -static const struct spi_nor_controller_ops aspeed_smc_controller_ops = { - .prepare = aspeed_smc_prep, - .unprepare = aspeed_smc_unprep, - .read_reg = aspeed_smc_read_reg, - .write_reg = aspeed_smc_write_reg, - .read = aspeed_smc_read_user, - .write = aspeed_smc_write_user, -}; - -static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller, - struct device_node *np, struct resource *r) -{ - const struct spi_nor_hwcaps hwcaps = { - .mask = SNOR_HWCAPS_READ | - SNOR_HWCAPS_READ_FAST | - SNOR_HWCAPS_PP, - }; - const struct aspeed_smc_info *info = controller->info; - struct device *dev = controller->dev; - struct device_node *child; - unsigned int cs; - int ret = -ENODEV; - bool found_one = false; - - for_each_available_child_of_node(np, child) { - struct aspeed_smc_chip *chip; - struct spi_nor *nor; - struct mtd_info *mtd; - - /* This driver does not support NAND or NOR flash devices. */ - if (!of_device_is_compatible(child, "jedec,spi-nor")) - continue; - - ret = of_property_read_u32(child, "reg", &cs); - if (ret) { - dev_err(dev, "Couldn't not read chip select.\n"); - break; - } - - if (cs >= info->nce) { - dev_err(dev, "Chip select %d out of range.\n", - cs); - ret = -ERANGE; - break; - } - - if (controller->chips[cs]) { - dev_err(dev, "Chip select %d already in use by %s\n", - cs, dev_name(controller->chips[cs]->nor.dev)); - ret = -EBUSY; - break; - } - - chip = devm_kzalloc(controller->dev, sizeof(*chip), GFP_KERNEL); - if (!chip) { - ret = -ENOMEM; - break; - } - - chip->controller = controller; - chip->ctl = controller->regs + info->ctl0 + cs * 4; - chip->cs = cs; - - nor = &chip->nor; - mtd = &nor->mtd; - - nor->dev = dev; - nor->priv = chip; - spi_nor_set_flash_node(nor, child); - nor->controller_ops = &aspeed_smc_controller_ops; - - ret = aspeed_smc_chip_setup_init(chip, r); - if (ret) - break; - - /* - * TODO: Add support for Dual and Quad SPI protocols - * attach when board support is present as determined - * by of property. - */ - ret = spi_nor_scan(nor, NULL, &hwcaps); - /* - * If we fail to scan the device it might not be present or - * broken. Don't fail the whole controller if others work. - */ - if (ret) { - if (found_one) - ret = 0; - - devm_kfree(controller->dev, chip); - continue; - } - - ret = aspeed_smc_chip_setup_finish(chip); - if (ret) - break; - - ret = mtd_device_register(mtd, NULL, 0); - if (ret) - break; - - controller->chips[cs] = chip; - found_one = true; - } - - if (ret) { - of_node_put(child); - aspeed_smc_unregister(controller); - } - - return ret; -} - -static int aspeed_smc_probe(struct platform_device *pdev) -{ - struct device_node *np = pdev->dev.of_node; - struct device *dev = &pdev->dev; - struct aspeed_smc_controller *controller; - const struct of_device_id *match; - const struct aspeed_smc_info *info; - struct resource *res; - int ret; - - match = of_match_device(aspeed_smc_matches, &pdev->dev); - if (!match || !match->data) - return -ENODEV; - info = match->data; - - controller = devm_kzalloc(&pdev->dev, - struct_size(controller, chips, info->nce), - GFP_KERNEL); - if (!controller) - return -ENOMEM; - controller->info = info; - controller->dev = dev; - - mutex_init(&controller->mutex); - platform_set_drvdata(pdev, controller); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - controller->regs = devm_ioremap_resource(dev, res); - if (IS_ERR(controller->regs)) - return PTR_ERR(controller->regs); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - controller->ahb_base = devm_ioremap_resource(dev, res); - if (IS_ERR(controller->ahb_base)) - return PTR_ERR(controller->ahb_base); - - controller->ahb_window_size = resource_size(res); - - ret = aspeed_smc_setup_flash(controller, np, res); - if (ret) - dev_err(dev, "Aspeed SMC probe failed %d\n", ret); - - return ret; -} - -static struct platform_driver aspeed_smc_driver = { - .probe = aspeed_smc_probe, - .remove = aspeed_smc_remove, - .driver = { - .name = DEVICE_NAME, - .of_match_table = aspeed_smc_matches, - } -}; - -module_platform_driver(aspeed_smc_driver); - -MODULE_DESCRIPTION("ASPEED Static Memory Controller Driver"); -MODULE_AUTHOR("Cedric Le Goater "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index b4f141ad9c9c..502967c76c5f 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -307,6 +307,52 @@ ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len, return nor->controller_ops->write(nor, to, len, buf); } +/** + * spi_nor_read_any_reg() - read any register from flash memory, nonvolatile or + * volatile. + * @nor: pointer to 'struct spi_nor'. + * @op: SPI memory operation. op->data.buf must be DMA-able. + * @proto: SPI protocol to use for the register operation. + * + * Return: zero on success, -errno otherwise + */ +int spi_nor_read_any_reg(struct spi_nor *nor, struct spi_mem_op *op, + enum spi_nor_protocol proto) +{ + if (!nor->spimem) + return -EOPNOTSUPP; + + spi_nor_spimem_setup_op(nor, op, proto); + return spi_nor_spimem_exec_op(nor, op); +} + +/** + * spi_nor_write_any_volatile_reg() - write any volatile register to flash + * memory. + * @nor: pointer to 'struct spi_nor' + * @op: SPI memory operation. op->data.buf must be DMA-able. + * @proto: SPI protocol to use for the register operation. + * + * Writing volatile registers are instant according to some manufacturers + * (Cypress, Micron) and do not need any status polling. + * + * Return: zero on success, -errno otherwise + */ +int spi_nor_write_any_volatile_reg(struct spi_nor *nor, struct spi_mem_op *op, + enum spi_nor_protocol proto) +{ + int ret; + + if (!nor->spimem) + return -EOPNOTSUPP; + + ret = spi_nor_write_enable(nor); + if (ret) + return ret; + spi_nor_spimem_setup_op(nor, op, proto); + return spi_nor_spimem_exec_op(nor, op); +} + /** * spi_nor_write_enable() - Set write enable latch with Write Enable command. * @nor: pointer to 'struct spi_nor'. @@ -318,11 +364,7 @@ int spi_nor_write_enable(struct spi_nor *nor) int ret; if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREN, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_NO_DATA); + struct spi_mem_op op = SPI_NOR_WREN_OP; spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); @@ -349,11 +391,7 @@ int spi_nor_write_disable(struct spi_nor *nor) int ret; if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRDI, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_NO_DATA); + struct spi_mem_op op = SPI_NOR_WRDI_OP; spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); @@ -369,6 +407,37 @@ int spi_nor_write_disable(struct spi_nor *nor) return ret; } +/** + * spi_nor_read_id() - Read the JEDEC ID. + * @nor: pointer to 'struct spi_nor'. + * @naddr: number of address bytes to send. Can be zero if the operation + * does not need to send an address. + * @ndummy: number of dummy bytes to send after an opcode or address. Can + * be zero if the operation does not require dummy bytes. + * @id: pointer to a DMA-able buffer where the value of the JEDEC ID + * will be written. + * @proto: the SPI protocol for register operation. + * + * Return: 0 on success, -errno otherwise. + */ +int spi_nor_read_id(struct spi_nor *nor, u8 naddr, u8 ndummy, u8 *id, + enum spi_nor_protocol proto) +{ + int ret; + + if (nor->spimem) { + struct spi_mem_op op = + SPI_NOR_READID_OP(naddr, ndummy, id, SPI_NOR_MAX_ID_LEN); + + spi_nor_spimem_setup_op(nor, &op, proto); + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDID, id, + SPI_NOR_MAX_ID_LEN); + } + return ret; +} + /** * spi_nor_read_sr() - Read the Status Register. * @nor: pointer to 'struct spi_nor'. @@ -382,11 +451,7 @@ int spi_nor_read_sr(struct spi_nor *nor, u8 *sr) int ret; if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_IN(1, sr, 0)); + struct spi_mem_op op = SPI_NOR_RDSR_OP(sr); if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) { op.addr.nbytes = nor->params->rdsr_addr_nbytes; @@ -426,11 +491,7 @@ int spi_nor_read_cr(struct spi_nor *nor, u8 *cr) int ret; if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDCR, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_IN(1, cr, 0)); + struct spi_mem_op op = SPI_NOR_RDCR_OP(cr); spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); @@ -459,14 +520,7 @@ int spi_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable) int ret; if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(enable ? - SPINOR_OP_EN4B : - SPINOR_OP_EX4B, - 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_NO_DATA); + struct spi_mem_op op = SPI_NOR_EN4B_EX4B_OP(enable); spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); @@ -500,11 +554,7 @@ static int spansion_set_4byte_addr_mode(struct spi_nor *nor, bool enable) nor->bouncebuf[0] = enable << 7; if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_BRWR, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 0)); + struct spi_mem_op op = SPI_NOR_BRWR_OP(nor->bouncebuf); spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); @@ -520,40 +570,6 @@ static int spansion_set_4byte_addr_mode(struct spi_nor *nor, bool enable) return ret; } -/** - * spi_nor_write_ear() - Write Extended Address Register. - * @nor: pointer to 'struct spi_nor'. - * @ear: value to write to the Extended Address Register. - * - * Return: 0 on success, -errno otherwise. - */ -int spi_nor_write_ear(struct spi_nor *nor, u8 ear) -{ - int ret; - - nor->bouncebuf[0] = ear; - - if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREAR, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 0)); - - spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); - - ret = spi_mem_exec_op(nor->spimem, &op); - } else { - ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WREAR, - nor->bouncebuf, 1); - } - - if (ret) - dev_dbg(nor->dev, "error %d writing EAR\n", ret); - - return ret; -} - /** * spi_nor_sr_ready() - Query the Status Register to see if the flash is ready * for new commands. @@ -649,11 +665,7 @@ int spi_nor_global_block_unlock(struct spi_nor *nor) return ret; if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_GBULK, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_NO_DATA); + struct spi_mem_op op = SPI_NOR_GBULK_OP; spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); @@ -688,11 +700,7 @@ int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len) return ret; if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(len, sr, 0)); + struct spi_mem_op op = SPI_NOR_WRSR_OP(sr, len); spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); @@ -788,6 +796,15 @@ static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1) if (ret) return ret; + ret = spi_nor_read_sr(nor, sr_cr); + if (ret) + return ret; + + if (sr1 != sr_cr[0]) { + dev_dbg(nor->dev, "SR: Read back test failed\n"); + return -EIO; + } + if (nor->flags & SNOR_F_NO_READ_CR) return 0; @@ -892,11 +909,7 @@ static int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2) return ret; if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR2, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(1, sr2, 0)); + struct spi_mem_op op = SPI_NOR_WRSR2_OP(sr2); spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); @@ -928,11 +941,7 @@ static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2) int ret; if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR2, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_IN(1, sr2, 0)); + struct spi_mem_op op = SPI_NOR_RDSR2_OP(sr2); spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); @@ -961,11 +970,7 @@ static int spi_nor_erase_chip(struct spi_nor *nor) dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd.size >> 10)); if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CHIP_ERASE, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_NO_DATA); + struct spi_mem_op op = SPI_NOR_CHIP_ERASE_OP; spi_nor_spimem_setup_op(nor, &op, nor->write_proto); @@ -1107,10 +1112,8 @@ int spi_nor_erase_sector(struct spi_nor *nor, u32 addr) if (nor->spimem) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(nor->erase_opcode, 0), - SPI_MEM_OP_ADDR(nor->addr_width, addr, 0), - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_NO_DATA); + SPI_NOR_SECTOR_ERASE_OP(nor->erase_opcode, + nor->addr_width, addr); spi_nor_spimem_setup_op(nor, &op, nor->write_proto); @@ -1629,58 +1632,45 @@ static const struct spi_nor_manufacturer *manufacturers[] = { &spi_nor_xmc, }; -static const struct flash_info * -spi_nor_search_part_by_id(const struct flash_info *parts, unsigned int nparts, - const u8 *id) +static const struct flash_info *spi_nor_match_id(struct spi_nor *nor, + const u8 *id) { - unsigned int i; + const struct flash_info *part; + unsigned int i, j; - for (i = 0; i < nparts; i++) { - if (parts[i].id_len && - !memcmp(parts[i].id, id, parts[i].id_len)) - return &parts[i]; + for (i = 0; i < ARRAY_SIZE(manufacturers); i++) { + for (j = 0; j < manufacturers[i]->nparts; j++) { + part = &manufacturers[i]->parts[j]; + if (part->id_len && + !memcmp(part->id, id, part->id_len)) { + nor->manufacturer = manufacturers[i]; + return part; + } + } } return NULL; } -static const struct flash_info *spi_nor_read_id(struct spi_nor *nor) +static const struct flash_info *spi_nor_detect(struct spi_nor *nor) { const struct flash_info *info; u8 *id = nor->bouncebuf; - unsigned int i; int ret; - if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 1), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_IN(SPI_NOR_MAX_ID_LEN, id, 1)); - - ret = spi_mem_exec_op(nor->spimem, &op); - } else { - ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDID, id, - SPI_NOR_MAX_ID_LEN); - } + ret = spi_nor_read_id(nor, 0, 0, id, nor->reg_proto); if (ret) { dev_dbg(nor->dev, "error %d reading JEDEC ID\n", ret); return ERR_PTR(ret); } - for (i = 0; i < ARRAY_SIZE(manufacturers); i++) { - info = spi_nor_search_part_by_id(manufacturers[i]->parts, - manufacturers[i]->nparts, - id); - if (info) { - nor->manufacturer = manufacturers[i]; - return info; - } + info = spi_nor_match_id(nor, id); + if (!info) { + dev_err(nor->dev, "unrecognized JEDEC id bytes: %*ph\n", + SPI_NOR_MAX_ID_LEN, id); + return ERR_PTR(-ENODEV); } - - dev_err(nor->dev, "unrecognized JEDEC id bytes: %*ph\n", - SPI_NOR_MAX_ID_LEN, id); - return ERR_PTR(-ENODEV); + return info; } static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len, @@ -1860,7 +1850,7 @@ int spi_nor_hwcaps_read2cmd(u32 hwcaps) ARRAY_SIZE(hwcaps_read2cmd)); } -static int spi_nor_hwcaps_pp2cmd(u32 hwcaps) +int spi_nor_hwcaps_pp2cmd(u32 hwcaps) { static const int hwcaps_pp2cmd[][2] = { { SNOR_HWCAPS_PP, SNOR_CMD_PP }, @@ -1919,10 +1909,7 @@ static int spi_nor_spimem_check_op(struct spi_nor *nor, static int spi_nor_spimem_check_readop(struct spi_nor *nor, const struct spi_nor_read_command *read) { - struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(read->opcode, 0), - SPI_MEM_OP_ADDR(3, 0, 0), - SPI_MEM_OP_DUMMY(1, 0), - SPI_MEM_OP_DATA_IN(2, NULL, 0)); + struct spi_mem_op op = SPI_NOR_READ_OP(read->opcode); spi_nor_spimem_setup_op(nor, &op, read->proto); @@ -1945,10 +1932,7 @@ static int spi_nor_spimem_check_readop(struct spi_nor *nor, static int spi_nor_spimem_check_pp(struct spi_nor *nor, const struct spi_nor_pp_command *pp) { - struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(pp->opcode, 0), - SPI_MEM_OP_ADDR(3, 0, 0), - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(2, NULL, 0)); + struct spi_mem_op op = SPI_NOR_PP_OP(pp->opcode); spi_nor_spimem_setup_op(nor, &op, pp->proto); @@ -2772,10 +2756,7 @@ static void spi_nor_soft_reset(struct spi_nor *nor) struct spi_mem_op op; int ret; - op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_SRSTEN, 0), - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DATA); + op = (struct spi_mem_op)SPINOR_SRSTEN_OP; spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); @@ -2785,10 +2766,7 @@ static void spi_nor_soft_reset(struct spi_nor *nor) return; } - op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_SRST, 0), - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DATA); + op = (struct spi_mem_op)SPINOR_SRST_OP; spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); @@ -2876,8 +2854,8 @@ void spi_nor_restore(struct spi_nor *nor) } EXPORT_SYMBOL_GPL(spi_nor_restore); -static const struct flash_info *spi_nor_match_id(struct spi_nor *nor, - const char *name) +static const struct flash_info *spi_nor_match_name(struct spi_nor *nor, + const char *name) { unsigned int i, j; @@ -2899,12 +2877,10 @@ static const struct flash_info *spi_nor_get_flash_info(struct spi_nor *nor, const struct flash_info *info = NULL; if (name) - info = spi_nor_match_id(nor, name); + info = spi_nor_match_name(nor, name); /* Try to auto-detect if chip name wasn't specified or not found */ if (!info) - info = spi_nor_read_id(nor); - if (IS_ERR_OR_NULL(info)) - return ERR_PTR(-ENOENT); + return spi_nor_detect(nor); /* * If caller has specified name of flash model that can normally be @@ -2913,7 +2889,7 @@ static const struct flash_info *spi_nor_get_flash_info(struct spi_nor *nor, if (name && info->id_len) { const struct flash_info *jinfo; - jinfo = spi_nor_read_id(nor); + jinfo = spi_nor_detect(nor); if (IS_ERR(jinfo)) { return jinfo; } else if (jinfo != info) { @@ -3156,6 +3132,8 @@ static int spi_nor_probe(struct spi_mem *spimem) if (ret) return ret; + spi_nor_debugfs_register(nor); + /* * None of the existing parts have > 512B pages, but let's play safe * and add this logic so that if anyone ever adds support for such diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index b7fd760e3b47..3f841ec36e56 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -11,6 +11,110 @@ #define SPI_NOR_MAX_ID_LEN 6 +/* Standard SPI NOR flash operations. */ +#define SPI_NOR_READID_OP(naddr, ndummy, buf, len) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 0), \ + SPI_MEM_OP_ADDR(naddr, 0, 0), \ + SPI_MEM_OP_DUMMY(ndummy, 0), \ + SPI_MEM_OP_DATA_IN(len, buf, 0)) + +#define SPI_NOR_WREN_OP \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREN, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_NO_DATA) + +#define SPI_NOR_WRDI_OP \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRDI, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_NO_DATA) + +#define SPI_NOR_RDSR_OP(buf) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_IN(1, buf, 0)) + +#define SPI_NOR_WRSR_OP(buf, len) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_OUT(len, buf, 0)) + +#define SPI_NOR_RDSR2_OP(buf) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR2, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_OUT(1, buf, 0)) + +#define SPI_NOR_WRSR2_OP(buf) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR2, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_OUT(1, buf, 0)) + +#define SPI_NOR_RDCR_OP(buf) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDCR, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_IN(1, buf, 0)) + +#define SPI_NOR_EN4B_EX4B_OP(enable) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_NO_DATA) + +#define SPI_NOR_BRWR_OP(buf) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_BRWR, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_OUT(1, buf, 0)) + +#define SPI_NOR_GBULK_OP \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_GBULK, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_NO_DATA) + +#define SPI_NOR_CHIP_ERASE_OP \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CHIP_ERASE, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_NO_DATA) + +#define SPI_NOR_SECTOR_ERASE_OP(opcode, addr_width, addr) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0), \ + SPI_MEM_OP_ADDR(addr_width, addr, 0), \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_NO_DATA) + +#define SPI_NOR_READ_OP(opcode) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0), \ + SPI_MEM_OP_ADDR(3, 0, 0), \ + SPI_MEM_OP_DUMMY(1, 0), \ + SPI_MEM_OP_DATA_IN(2, NULL, 0)) + +#define SPI_NOR_PP_OP(opcode) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0), \ + SPI_MEM_OP_ADDR(3, 0, 0), \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_OUT(2, NULL, 0)) + +#define SPINOR_SRSTEN_OP \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_SRSTEN, 0), \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DATA) + +#define SPINOR_SRST_OP \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_SRST, 0), \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DATA) + +/* Keep these in sync with the list in debugfs.c */ enum spi_nor_option_flags { SNOR_F_HAS_SR_TB = BIT(0), SNOR_F_NO_OP_CHIP_ERASE = BIT(1), @@ -236,9 +340,10 @@ struct spi_nor_otp { * @writesize Minimal writable flash unit size. Defaults to 1. Set to * ECC unit size for ECC-ed flashes. * @page_size: the page size of the SPI NOR flash memory. - * @rdsr_dummy: dummy cycles needed for Read Status Register command. + * @rdsr_dummy: dummy cycles needed for Read Status Register command + * in octal DTR mode. * @rdsr_addr_nbytes: dummy address bytes needed for Read Status Register - * command. + * command in octal DTR mode. * @hwcaps: describes the read and page program hardware * capabilities. * @reads: read capabilities ordered by priority: the higher index @@ -526,7 +631,6 @@ void spi_nor_spimem_setup_op(const struct spi_nor *nor, int spi_nor_write_enable(struct spi_nor *nor); int spi_nor_write_disable(struct spi_nor *nor); int spi_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable); -int spi_nor_write_ear(struct spi_nor *nor, u8 ear); int spi_nor_wait_till_ready(struct spi_nor *nor); int spi_nor_global_block_unlock(struct spi_nor *nor); int spi_nor_lock_and_prep(struct spi_nor *nor); @@ -534,6 +638,8 @@ void spi_nor_unlock_and_unprep(struct spi_nor *nor); int spi_nor_sr1_bit6_quad_enable(struct spi_nor *nor); int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor); int spi_nor_sr2_bit7_quad_enable(struct spi_nor *nor); +int spi_nor_read_id(struct spi_nor *nor, u8 naddr, u8 ndummy, u8 *id, + enum spi_nor_protocol reg_proto); int spi_nor_read_sr(struct spi_nor *nor, u8 *sr); int spi_nor_sr_ready(struct spi_nor *nor); int spi_nor_read_cr(struct spi_nor *nor, u8 *cr); @@ -545,6 +651,10 @@ ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len, u8 *buf); ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len, const u8 *buf); +int spi_nor_read_any_reg(struct spi_nor *nor, struct spi_mem_op *op, + enum spi_nor_protocol proto); +int spi_nor_write_any_volatile_reg(struct spi_nor *nor, struct spi_mem_op *op, + enum spi_nor_protocol proto); int spi_nor_erase_sector(struct spi_nor *nor, u32 addr); int spi_nor_otp_read_secr(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf); @@ -555,6 +665,7 @@ int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region); int spi_nor_otp_is_locked_sr2(struct spi_nor *nor, unsigned int region); int spi_nor_hwcaps_read2cmd(u32 hwcaps); +int spi_nor_hwcaps_pp2cmd(u32 hwcaps); u8 spi_nor_convert_3to4_read(u8 opcode); void spi_nor_set_read_settings(struct spi_nor_read_command *read, u8 num_mode_clocks, @@ -590,4 +701,10 @@ static inline struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd) return container_of(mtd, struct spi_nor, mtd); } +#ifdef CONFIG_DEBUG_FS +void spi_nor_debugfs_register(struct spi_nor *nor); +#else +static inline void spi_nor_debugfs_register(struct spi_nor *nor) {} +#endif + #endif /* __LINUX_MTD_SPI_NOR_INTERNAL_H */ diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c new file mode 100644 index 000000000000..eaf84f7a0676 --- /dev/null +++ b/drivers/mtd/spi-nor/debugfs.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include + +#include "core.h" + +#define SPI_NOR_DEBUGFS_ROOT "spi-nor" + +#define SNOR_F_NAME(name) [ilog2(SNOR_F_##name)] = #name +static const char *const snor_f_names[] = { + SNOR_F_NAME(HAS_SR_TB), + SNOR_F_NAME(NO_OP_CHIP_ERASE), + SNOR_F_NAME(BROKEN_RESET), + SNOR_F_NAME(4B_OPCODES), + SNOR_F_NAME(HAS_4BAIT), + SNOR_F_NAME(HAS_LOCK), + SNOR_F_NAME(HAS_16BIT_SR), + SNOR_F_NAME(NO_READ_CR), + SNOR_F_NAME(HAS_SR_TB_BIT6), + SNOR_F_NAME(HAS_4BIT_BP), + SNOR_F_NAME(HAS_SR_BP3_BIT6), + SNOR_F_NAME(IO_MODE_EN_VOLATILE), + SNOR_F_NAME(SOFT_RESET), + SNOR_F_NAME(SWP_IS_VOLATILE), +}; +#undef SNOR_F_NAME + +static const char *spi_nor_protocol_name(enum spi_nor_protocol proto) +{ + switch (proto) { + case SNOR_PROTO_1_1_1: return "1S-1S-1S"; + case SNOR_PROTO_1_1_2: return "1S-1S-2S"; + case SNOR_PROTO_1_1_4: return "1S-1S-4S"; + case SNOR_PROTO_1_1_8: return "1S-1S-8S"; + case SNOR_PROTO_1_2_2: return "1S-2S-2S"; + case SNOR_PROTO_1_4_4: return "1S-4S-4S"; + case SNOR_PROTO_1_8_8: return "1S-8S-8S"; + case SNOR_PROTO_2_2_2: return "2S-2S-2S"; + case SNOR_PROTO_4_4_4: return "4S-4S-4S"; + case SNOR_PROTO_8_8_8: return "8S-8S-8S"; + case SNOR_PROTO_1_1_1_DTR: return "1D-1D-1D"; + case SNOR_PROTO_1_2_2_DTR: return "1D-2D-2D"; + case SNOR_PROTO_1_4_4_DTR: return "1D-4D-4D"; + case SNOR_PROTO_1_8_8_DTR: return "1D-8D-8D"; + case SNOR_PROTO_8_8_8_DTR: return "8D-8D-8D"; + } + + return ""; +} + +static void spi_nor_print_flags(struct seq_file *s, unsigned long flags, + const char *const *names, int names_len) +{ + bool sep = false; + int i; + + for (i = 0; i < sizeof(flags) * BITS_PER_BYTE; i++) { + if (!(flags & BIT(i))) + continue; + if (sep) + seq_puts(s, " | "); + sep = true; + if (i < names_len && names[i]) + seq_puts(s, names[i]); + else + seq_printf(s, "1<<%d", i); + } +} + +static int spi_nor_params_show(struct seq_file *s, void *data) +{ + struct spi_nor *nor = s->private; + struct spi_nor_flash_parameter *params = nor->params; + struct spi_nor_erase_map *erase_map = ¶ms->erase_map; + struct spi_nor_erase_region *region; + const struct flash_info *info = nor->info; + char buf[16], *str; + int i; + + seq_printf(s, "name\t\t%s\n", info->name); + seq_printf(s, "id\t\t%*ph\n", info->id_len, info->id); + string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf)); + seq_printf(s, "size\t\t%s\n", buf); + seq_printf(s, "write size\t%u\n", params->writesize); + seq_printf(s, "page size\t%u\n", params->page_size); + seq_printf(s, "address width\t%u\n", nor->addr_width); + + seq_puts(s, "flags\t\t"); + spi_nor_print_flags(s, nor->flags, snor_f_names, sizeof(snor_f_names)); + seq_puts(s, "\n"); + + seq_puts(s, "\nopcodes\n"); + seq_printf(s, " read\t\t0x%02x\n", nor->read_opcode); + seq_printf(s, " dummy cycles\t%u\n", nor->read_dummy); + seq_printf(s, " erase\t\t0x%02x\n", nor->erase_opcode); + seq_printf(s, " program\t0x%02x\n", nor->program_opcode); + + switch (nor->cmd_ext_type) { + case SPI_NOR_EXT_NONE: + str = "none"; + break; + case SPI_NOR_EXT_REPEAT: + str = "repeat"; + break; + case SPI_NOR_EXT_INVERT: + str = "invert"; + break; + default: + str = ""; + break; + } + seq_printf(s, " 8D extension\t%s\n", str); + + seq_puts(s, "\nprotocols\n"); + seq_printf(s, " read\t\t%s\n", + spi_nor_protocol_name(nor->read_proto)); + seq_printf(s, " write\t\t%s\n", + spi_nor_protocol_name(nor->write_proto)); + seq_printf(s, " register\t%s\n", + spi_nor_protocol_name(nor->reg_proto)); + + seq_puts(s, "\nerase commands\n"); + for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) { + struct spi_nor_erase_type *et = &erase_map->erase_type[i]; + + if (et->size) { + string_get_size(et->size, 1, STRING_UNITS_2, buf, + sizeof(buf)); + seq_printf(s, " %02x (%s) [%d]\n", et->opcode, buf, i); + } + } + + if (!(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) { + string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf)); + seq_printf(s, " %02x (%s)\n", SPINOR_OP_CHIP_ERASE, buf); + } + + seq_puts(s, "\nsector map\n"); + seq_puts(s, " region (in hex) | erase mask | flags\n"); + seq_puts(s, " ------------------+------------+----------\n"); + for (region = erase_map->regions; + region; + region = spi_nor_region_next(region)) { + u64 start = region->offset & ~SNOR_ERASE_FLAGS_MASK; + u64 flags = region->offset & SNOR_ERASE_FLAGS_MASK; + u64 end = start + region->size - 1; + + seq_printf(s, " %08llx-%08llx | [%c%c%c%c] | %s\n", + start, end, + flags & BIT(0) ? '0' : ' ', + flags & BIT(1) ? '1' : ' ', + flags & BIT(2) ? '2' : ' ', + flags & BIT(3) ? '3' : ' ', + flags & SNOR_OVERLAID_REGION ? "overlaid" : ""); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(spi_nor_params); + +static void spi_nor_print_read_cmd(struct seq_file *s, u32 cap, + struct spi_nor_read_command *cmd) +{ + seq_printf(s, " %s%s\n", spi_nor_protocol_name(cmd->proto), + cap == SNOR_HWCAPS_READ_FAST ? " (fast read)" : ""); + seq_printf(s, " opcode\t0x%02x\n", cmd->opcode); + seq_printf(s, " mode cycles\t%u\n", cmd->num_mode_clocks); + seq_printf(s, " dummy cycles\t%u\n", cmd->num_wait_states); +} + +static void spi_nor_print_pp_cmd(struct seq_file *s, + struct spi_nor_pp_command *cmd) +{ + seq_printf(s, " %s\n", spi_nor_protocol_name(cmd->proto)); + seq_printf(s, " opcode\t0x%02x\n", cmd->opcode); +} + +static int spi_nor_capabilities_show(struct seq_file *s, void *data) +{ + struct spi_nor *nor = s->private; + struct spi_nor_flash_parameter *params = nor->params; + u32 hwcaps = params->hwcaps.mask; + int i, cmd; + + seq_puts(s, "Supported read modes by the flash\n"); + for (i = 0; i < sizeof(hwcaps) * BITS_PER_BYTE; i++) { + if (!(hwcaps & BIT(i))) + continue; + + cmd = spi_nor_hwcaps_read2cmd(BIT(i)); + if (cmd < 0) + continue; + + spi_nor_print_read_cmd(s, BIT(i), ¶ms->reads[cmd]); + hwcaps &= ~BIT(i); + } + + seq_puts(s, "\nSupported page program modes by the flash\n"); + for (i = 0; i < sizeof(hwcaps) * BITS_PER_BYTE; i++) { + if (!(hwcaps & BIT(i))) + continue; + + cmd = spi_nor_hwcaps_pp2cmd(BIT(i)); + if (cmd < 0) + continue; + + spi_nor_print_pp_cmd(s, ¶ms->page_programs[cmd]); + hwcaps &= ~BIT(i); + } + + if (hwcaps) + seq_printf(s, "\nunknown hwcaps 0x%x\n", hwcaps); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(spi_nor_capabilities); + +static void spi_nor_debugfs_unregister(void *data) +{ + struct spi_nor *nor = data; + + debugfs_remove(nor->debugfs_root); + nor->debugfs_root = NULL; +} + +void spi_nor_debugfs_register(struct spi_nor *nor) +{ + struct dentry *rootdir, *d; + int ret; + + /* Create rootdir once. Will never be deleted again. */ + rootdir = debugfs_lookup(SPI_NOR_DEBUGFS_ROOT, NULL); + if (!rootdir) + rootdir = debugfs_create_dir(SPI_NOR_DEBUGFS_ROOT, NULL); + + ret = devm_add_action(nor->dev, spi_nor_debugfs_unregister, nor); + if (ret) + return; + + d = debugfs_create_dir(dev_name(nor->dev), rootdir); + nor->debugfs_root = d; + + debugfs_create_file("params", 0444, d, nor, &spi_nor_params_fops); + debugfs_create_file("capabilities", 0444, d, nor, + &spi_nor_capabilities_fops); +} diff --git a/drivers/mtd/spi-nor/eon.c b/drivers/mtd/spi-nor/eon.c index 8c1c57530281..50a11053711f 100644 --- a/drivers/mtd/spi-nor/eon.c +++ b/drivers/mtd/spi-nor/eon.c @@ -25,7 +25,8 @@ static const struct flash_info eon_nor_parts[] = { { "en25qh64", INFO(0x1c7017, 0, 64 * 1024, 128) NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ) }, { "en25qh128", INFO(0x1c7018, 0, 64 * 1024, 256) }, - { "en25qh256", INFO(0x1c7019, 0, 64 * 1024, 512) }, + { "en25qh256", INFO(0x1c7019, 0, 64 * 1024, 512) + PARSE_SFDP }, { "en25s64", INFO(0x1c3817, 0, 64 * 1024, 128) NO_SFDP_FLAGS(SECT_4K) }, }; diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c index 8a20475ce77a..a96f74e0f568 100644 --- a/drivers/mtd/spi-nor/micron-st.c +++ b/drivers/mtd/spi-nor/micron-st.c @@ -28,82 +28,52 @@ #define FSR_P_ERR BIT(4) /* Program operation status */ #define FSR_PT_ERR BIT(1) /* Protection error bit */ -static int micron_st_nor_octal_dtr_enable(struct spi_nor *nor, bool enable) +/* Micron ST SPI NOR flash operations. */ +#define MICRON_ST_NOR_WR_ANY_REG_OP(naddr, addr, ndata, buf) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_MT_WR_ANY_REG, 0), \ + SPI_MEM_OP_ADDR(naddr, addr, 0), \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_OUT(ndata, buf, 0)) + +#define MICRON_ST_RDFSR_OP(buf) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDFSR, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_IN(1, buf, 0)) + +#define MICRON_ST_CLFSR_OP \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLFSR, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_NO_DATA) + +static int micron_st_nor_octal_dtr_en(struct spi_nor *nor) { struct spi_mem_op op; u8 *buf = nor->bouncebuf; int ret; - if (enable) { - /* Use 20 dummy cycles for memory array reads. */ - ret = spi_nor_write_enable(nor); - if (ret) - return ret; - - *buf = 20; - op = (struct spi_mem_op) - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_MT_WR_ANY_REG, 1), - SPI_MEM_OP_ADDR(3, SPINOR_REG_MT_CFR1V, 1), - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(1, buf, 1)); - - ret = spi_mem_exec_op(nor->spimem, &op); - if (ret) - return ret; - - ret = spi_nor_wait_till_ready(nor); - if (ret) - return ret; - } - - ret = spi_nor_write_enable(nor); + /* Use 20 dummy cycles for memory array reads. */ + *buf = 20; + op = (struct spi_mem_op) + MICRON_ST_NOR_WR_ANY_REG_OP(3, SPINOR_REG_MT_CFR1V, 1, buf); + ret = spi_nor_write_any_volatile_reg(nor, &op, nor->reg_proto); if (ret) return ret; - if (enable) { - buf[0] = SPINOR_MT_OCT_DTR; - } else { - /* - * The register is 1-byte wide, but 1-byte transactions are not - * allowed in 8D-8D-8D mode. The next register is the dummy - * cycle configuration register. Since the transaction needs to - * be at least 2 bytes wide, set the next register to its - * default value. This also makes sense because the value was - * changed when enabling 8D-8D-8D mode, it should be reset when - * disabling. - */ - buf[0] = SPINOR_MT_EXSPI; - buf[1] = SPINOR_REG_MT_CFR1V_DEF; - } - + buf[0] = SPINOR_MT_OCT_DTR; op = (struct spi_mem_op) - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_MT_WR_ANY_REG, 1), - SPI_MEM_OP_ADDR(enable ? 3 : 4, - SPINOR_REG_MT_CFR0V, 1), - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(enable ? 1 : 2, buf, 1)); - - if (!enable) - spi_nor_spimem_setup_op(nor, &op, SNOR_PROTO_8_8_8_DTR); - - ret = spi_mem_exec_op(nor->spimem, &op); + MICRON_ST_NOR_WR_ANY_REG_OP(3, SPINOR_REG_MT_CFR0V, 1, buf); + ret = spi_nor_write_any_volatile_reg(nor, &op, nor->reg_proto); if (ret) return ret; /* Read flash ID to make sure the switch was successful. */ - op = (struct spi_mem_op) - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 1), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_DUMMY(enable ? 8 : 0, 1), - SPI_MEM_OP_DATA_IN(round_up(nor->info->id_len, 2), - buf, 1)); - - if (enable) - spi_nor_spimem_setup_op(nor, &op, SNOR_PROTO_8_8_8_DTR); - - ret = spi_mem_exec_op(nor->spimem, &op); - if (ret) + ret = spi_nor_read_id(nor, 0, 8, buf, SNOR_PROTO_8_8_8_DTR); + if (ret) { + dev_dbg(nor->dev, "error %d reading JEDEC ID after enabling 8D-8D-8D mode\n", ret); return ret; + } if (memcmp(buf, nor->info->id, nor->info->id_len)) return -EINVAL; @@ -111,6 +81,47 @@ static int micron_st_nor_octal_dtr_enable(struct spi_nor *nor, bool enable) return 0; } +static int micron_st_nor_octal_dtr_dis(struct spi_nor *nor) +{ + struct spi_mem_op op; + u8 *buf = nor->bouncebuf; + int ret; + + /* + * The register is 1-byte wide, but 1-byte transactions are not allowed + * in 8D-8D-8D mode. The next register is the dummy cycle configuration + * register. Since the transaction needs to be at least 2 bytes wide, + * set the next register to its default value. This also makes sense + * because the value was changed when enabling 8D-8D-8D mode, it should + * be reset when disabling. + */ + buf[0] = SPINOR_MT_EXSPI; + buf[1] = SPINOR_REG_MT_CFR1V_DEF; + op = (struct spi_mem_op) + MICRON_ST_NOR_WR_ANY_REG_OP(4, SPINOR_REG_MT_CFR0V, 2, buf); + ret = spi_nor_write_any_volatile_reg(nor, &op, SNOR_PROTO_8_8_8_DTR); + if (ret) + return ret; + + /* Read flash ID to make sure the switch was successful. */ + ret = spi_nor_read_id(nor, 0, 0, buf, SNOR_PROTO_1_1_1); + if (ret) { + dev_dbg(nor->dev, "error %d reading JEDEC ID after disabling 8D-8D-8D mode\n", ret); + return ret; + } + + if (memcmp(buf, nor->info->id, nor->info->id_len)) + return -EINVAL; + + return 0; +} + +static int micron_st_nor_octal_dtr_enable(struct spi_nor *nor, bool enable) +{ + return enable ? micron_st_nor_octal_dtr_en(nor) : + micron_st_nor_octal_dtr_dis(nor); +} + static void mt35xu512aba_default_init(struct spi_nor *nor) { nor->params->octal_dtr_enable = micron_st_nor_octal_dtr_enable; @@ -322,11 +333,7 @@ static int micron_st_nor_read_fsr(struct spi_nor *nor, u8 *fsr) int ret; if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDFSR, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_IN(1, fsr, 0)); + struct spi_mem_op op = MICRON_ST_RDFSR_OP(fsr); if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) { op.addr.nbytes = nor->params->rdsr_addr_nbytes; @@ -361,11 +368,7 @@ static void micron_st_nor_clear_fsr(struct spi_nor *nor) int ret; if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLFSR, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_NO_DATA); + struct spi_mem_op op = MICRON_ST_CLFSR_OP; spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index f24e546e04a5..43cd6cd92537 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -23,6 +23,96 @@ #define SPINOR_REG_CYPRESS_CFR5V_OCT_DTR_DS 0 #define SPINOR_OP_CYPRESS_RD_FAST 0xee +/* Cypress SPI NOR flash operations. */ +#define CYPRESS_NOR_WR_ANY_REG_OP(naddr, addr, ndata, buf) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WR_ANY_REG, 0), \ + SPI_MEM_OP_ADDR(naddr, addr, 0), \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_OUT(ndata, buf, 0)) + +#define CYPRESS_NOR_RD_ANY_REG_OP(naddr, addr, buf) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RD_ANY_REG, 0), \ + SPI_MEM_OP_ADDR(naddr, addr, 0), \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_IN(1, buf, 0)) + +#define SPANSION_CLSR_OP \ + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLSR, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_NO_DATA) + +static int cypress_nor_octal_dtr_en(struct spi_nor *nor) +{ + struct spi_mem_op op; + u8 *buf = nor->bouncebuf; + int ret; + + /* Use 24 dummy cycles for memory array reads. */ + *buf = SPINOR_REG_CYPRESS_CFR2V_MEMLAT_11_24; + op = (struct spi_mem_op) + CYPRESS_NOR_WR_ANY_REG_OP(3, SPINOR_REG_CYPRESS_CFR2V, 1, buf); + + ret = spi_nor_write_any_volatile_reg(nor, &op, nor->reg_proto); + if (ret) + return ret; + + nor->read_dummy = 24; + + /* Set the octal and DTR enable bits. */ + buf[0] = SPINOR_REG_CYPRESS_CFR5V_OCT_DTR_EN; + op = (struct spi_mem_op) + CYPRESS_NOR_WR_ANY_REG_OP(3, SPINOR_REG_CYPRESS_CFR5V, 1, buf); + + ret = spi_nor_write_any_volatile_reg(nor, &op, nor->reg_proto); + if (ret) + return ret; + + /* Read flash ID to make sure the switch was successful. */ + ret = spi_nor_read_id(nor, 4, 3, buf, SNOR_PROTO_8_8_8_DTR); + if (ret) { + dev_dbg(nor->dev, "error %d reading JEDEC ID after enabling 8D-8D-8D mode\n", ret); + return ret; + } + + if (memcmp(buf, nor->info->id, nor->info->id_len)) + return -EINVAL; + + return 0; +} + +static int cypress_nor_octal_dtr_dis(struct spi_nor *nor) +{ + struct spi_mem_op op; + u8 *buf = nor->bouncebuf; + int ret; + + /* + * The register is 1-byte wide, but 1-byte transactions are not allowed + * in 8D-8D-8D mode. Since there is no register at the next location, + * just initialize the value to 0 and let the transaction go on. + */ + buf[0] = SPINOR_REG_CYPRESS_CFR5V_OCT_DTR_DS; + buf[1] = 0; + op = (struct spi_mem_op) + CYPRESS_NOR_WR_ANY_REG_OP(4, SPINOR_REG_CYPRESS_CFR5V, 2, buf); + ret = spi_nor_write_any_volatile_reg(nor, &op, SNOR_PROTO_8_8_8_DTR); + if (ret) + return ret; + + /* Read flash ID to make sure the switch was successful. */ + ret = spi_nor_read_id(nor, 0, 0, buf, SNOR_PROTO_1_1_1); + if (ret) { + dev_dbg(nor->dev, "error %d reading JEDEC ID after disabling 8D-8D-8D mode\n", ret); + return ret; + } + + if (memcmp(buf, nor->info->id, nor->info->id_len)) + return -EINVAL; + + return 0; +} + /** * cypress_nor_octal_dtr_enable() - Enable octal DTR on Cypress flashes. * @nor: pointer to a 'struct spi_nor' @@ -35,87 +125,8 @@ */ static int cypress_nor_octal_dtr_enable(struct spi_nor *nor, bool enable) { - struct spi_mem_op op; - u8 *buf = nor->bouncebuf; - int ret; - - if (enable) { - /* Use 24 dummy cycles for memory array reads. */ - ret = spi_nor_write_enable(nor); - if (ret) - return ret; - - *buf = SPINOR_REG_CYPRESS_CFR2V_MEMLAT_11_24; - op = (struct spi_mem_op) - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WR_ANY_REG, 1), - SPI_MEM_OP_ADDR(3, SPINOR_REG_CYPRESS_CFR2V, - 1), - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(1, buf, 1)); - - ret = spi_mem_exec_op(nor->spimem, &op); - if (ret) - return ret; - - ret = spi_nor_wait_till_ready(nor); - if (ret) - return ret; - - nor->read_dummy = 24; - } - - /* Set/unset the octal and DTR enable bits. */ - ret = spi_nor_write_enable(nor); - if (ret) - return ret; - - if (enable) { - buf[0] = SPINOR_REG_CYPRESS_CFR5V_OCT_DTR_EN; - } else { - /* - * The register is 1-byte wide, but 1-byte transactions are not - * allowed in 8D-8D-8D mode. Since there is no register at the - * next location, just initialize the value to 0 and let the - * transaction go on. - */ - buf[0] = SPINOR_REG_CYPRESS_CFR5V_OCT_DTR_DS; - buf[1] = 0; - } - - op = (struct spi_mem_op) - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WR_ANY_REG, 1), - SPI_MEM_OP_ADDR(enable ? 3 : 4, - SPINOR_REG_CYPRESS_CFR5V, - 1), - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(enable ? 1 : 2, buf, 1)); - - if (!enable) - spi_nor_spimem_setup_op(nor, &op, SNOR_PROTO_8_8_8_DTR); - - ret = spi_mem_exec_op(nor->spimem, &op); - if (ret) - return ret; - - /* Read flash ID to make sure the switch was successful. */ - op = (struct spi_mem_op) - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 1), - SPI_MEM_OP_ADDR(enable ? 4 : 0, 0, 1), - SPI_MEM_OP_DUMMY(enable ? 3 : 0, 1), - SPI_MEM_OP_DATA_IN(round_up(nor->info->id_len, 2), - buf, 1)); - - if (enable) - spi_nor_spimem_setup_op(nor, &op, SNOR_PROTO_8_8_8_DTR); - - ret = spi_mem_exec_op(nor->spimem, &op); - if (ret) - return ret; - - if (memcmp(buf, nor->info->id, nor->info->id_len)) - return -EINVAL; - - return 0; + return enable ? cypress_nor_octal_dtr_en(nor) : + cypress_nor_octal_dtr_dis(nor); } static void s28hs512t_default_init(struct spi_nor *nor) @@ -162,12 +173,12 @@ static int s28hs512t_post_bfpt_fixup(struct spi_nor *nor, * CFR3V[4] and set the correct size. */ struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RD_ANY_REG, 1), - SPI_MEM_OP_ADDR(3, SPINOR_REG_CYPRESS_CFR3V, 1), - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 1)); + CYPRESS_NOR_RD_ANY_REG_OP(3, SPINOR_REG_CYPRESS_CFR3V, + nor->bouncebuf); int ret; + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + ret = spi_mem_exec_op(nor->spimem, &op); if (ret) return ret; @@ -317,11 +328,7 @@ static void spansion_nor_clear_sr(struct spi_nor *nor) int ret; if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLSR, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_NO_DATA); + struct spi_mem_op op = SPANSION_CLSR_OP; spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index fe80dffc2e70..ffaa24055259 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -8,6 +8,15 @@ #include "core.h" +#define WINBOND_NOR_OP_RDEAR 0xc8 /* Read Extended Address Register */ +#define WINBOND_NOR_OP_WREAR 0xc5 /* Write Extended Address Register */ + +#define WINBOND_NOR_WREAR_OP(buf) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(WINBOND_NOR_OP_WREAR, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_OUT(1, buf, 0)) + static int w25q256_post_bfpt_fixups(struct spi_nor *nor, const struct sfdp_parameter_header *bfpt_header, @@ -124,11 +133,45 @@ static const struct flash_info winbond_nor_parts[] = { { "w25m512jv", INFO(0xef7119, 0, 64 * 1024, 1024) NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ | SPI_NOR_DUAL_READ) }, + { "w25q512nwm", INFO(0xef8020, 0, 64 * 1024, 1024) + PARSE_SFDP + OTP_INFO(256, 3, 0x1000, 0x1000) }, { "w25q512jvq", INFO(0xef4020, 0, 64 * 1024, 1024) NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, }; +/** + * winbond_nor_write_ear() - Write Extended Address Register. + * @nor: pointer to 'struct spi_nor'. + * @ear: value to write to the Extended Address Register. + * + * Return: 0 on success, -errno otherwise. + */ +static int winbond_nor_write_ear(struct spi_nor *nor, u8 ear) +{ + int ret; + + nor->bouncebuf[0] = ear; + + if (nor->spimem) { + struct spi_mem_op op = WINBOND_NOR_WREAR_OP(nor->bouncebuf); + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = spi_nor_controller_ops_write_reg(nor, + WINBOND_NOR_OP_WREAR, + nor->bouncebuf, 1); + } + + if (ret) + dev_dbg(nor->dev, "error %d writing EAR\n", ret); + + return ret; +} + /** * winbond_nor_set_4byte_addr_mode() - Set 4-byte address mode for Winbond * flashes. @@ -155,7 +198,7 @@ static int winbond_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable) if (ret) return ret; - ret = spi_nor_write_ear(nor, 0); + ret = winbond_nor_write_ear(nor, 0); if (ret) return ret; diff --git a/drivers/mtd/spi-nor/xilinx.c b/drivers/mtd/spi-nor/xilinx.c index 9459ac2609dc..1d2f5db047bd 100644 --- a/drivers/mtd/spi-nor/xilinx.c +++ b/drivers/mtd/spi-nor/xilinx.c @@ -15,6 +15,12 @@ #define XSR_PAGESIZE BIT(0) /* Page size in Po2 or Linear */ #define XSR_RDY BIT(7) /* Ready */ +#define XILINX_RDSR_OP(buf) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(XILINX_OP_RDSR, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_IN(1, buf, 0)) + #define S3AN_INFO(_jedec_id, _n_sectors, _page_size) \ .id = { \ ((_jedec_id) >> 16) & 0xff, \ @@ -72,11 +78,7 @@ static int xilinx_nor_read_sr(struct spi_nor *nor, u8 *sr) int ret; if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(XILINX_OP_RDSR, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_IN(1, sr, 0)); + struct spi_mem_op op = XILINX_RDSR_OP(sr); spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); diff --git a/drivers/of/platform.c b/drivers/of/platform.c index a16b74f32aa9..55d62b82c650 100644 --- a/drivers/of/platform.c +++ b/drivers/of/platform.c @@ -509,6 +509,7 @@ EXPORT_SYMBOL_GPL(of_platform_default_populate); #ifndef CONFIG_PPC static const struct of_device_id reserved_mem_matches[] = { + { .compatible = "phram" }, { .compatible = "qcom,rmtfs-mem" }, { .compatible = "qcom,cmd-db" }, { .compatible = "qcom,smem" }, diff --git a/drivers/opp/of.c b/drivers/opp/of.c index 440ab5a03df9..485ea980bde7 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -1448,7 +1448,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_get_of_node); * Returns 0 on success or a proper -EINVAL value in case of error. */ static int __maybe_unused -_get_dt_power(unsigned long *mW, unsigned long *kHz, struct device *dev) +_get_dt_power(struct device *dev, unsigned long *mW, unsigned long *kHz) { struct dev_pm_opp *opp; unsigned long opp_freq, opp_power; @@ -1482,8 +1482,8 @@ _get_dt_power(unsigned long *mW, unsigned long *kHz, struct device *dev) * Returns -EINVAL if the power calculation failed because of missing * parameters, 0 otherwise. */ -static int __maybe_unused _get_power(unsigned long *mW, unsigned long *kHz, - struct device *dev) +static int __maybe_unused _get_power(struct device *dev, unsigned long *mW, + unsigned long *kHz) { struct dev_pm_opp *opp; struct device_node *np; diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index 1f15ab7eabf8..3787876ecb24 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -1374,6 +1374,9 @@ void pci_acpi_setup(struct device *dev, struct acpi_device *adev) acpi_pci_wakeup(pci_dev, false); acpi_device_power_add_dependent(adev, dev); + + if (pci_is_bridge(pci_dev)) + acpi_dev_power_up_children_with_adr(adev); } void pci_acpi_cleanup(struct device *dev, struct acpi_device *adev) diff --git a/drivers/powercap/dtpm_cpu.c b/drivers/powercap/dtpm_cpu.c index bca2f912d349..f5eced0842b3 100644 --- a/drivers/powercap/dtpm_cpu.c +++ b/drivers/powercap/dtpm_cpu.c @@ -211,7 +211,7 @@ static int __dtpm_cpu_setup(int cpu, struct dtpm *parent) return 0; pd = em_cpu_get(cpu); - if (!pd) + if (!pd || em_is_artificial(pd)) return -EINVAL; dtpm_cpu = kzalloc(sizeof(*dtpm_cpu), GFP_KERNEL); diff --git a/drivers/powercap/intel_rapl_common.c b/drivers/powercap/intel_rapl_common.c index 07611a00b78f..a9c99d9e8b42 100644 --- a/drivers/powercap/intel_rapl_common.c +++ b/drivers/powercap/intel_rapl_common.c @@ -1010,7 +1010,7 @@ static u64 rapl_compute_time_window_atom(struct rapl_package *rp, u64 value, * where time_unit is default to 1 sec. Never 0. */ if (!to_raw) - return (value) ? value *= rp->time_unit : rp->time_unit; + return (value) ? value * rp->time_unit : rp->time_unit; value = div64_u64(value, rp->time_unit); @@ -1107,6 +1107,8 @@ static const struct x86_cpu_id rapl_ids[] __initconst = { X86_MATCH_INTEL_FAM6_MODEL(ROCKETLAKE, &rapl_defaults_core), X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE, &rapl_defaults_core), X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, &rapl_defaults_core), + X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_N, &rapl_defaults_core), + X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE, &rapl_defaults_core), X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, &rapl_defaults_spr_server), X86_MATCH_INTEL_FAM6_MODEL(LAKEFIELD, &rapl_defaults_core), diff --git a/drivers/powercap/intel_rapl_msr.c b/drivers/powercap/intel_rapl_msr.c index 1be45f36ab6c..9d23984d8931 100644 --- a/drivers/powercap/intel_rapl_msr.c +++ b/drivers/powercap/intel_rapl_msr.c @@ -140,6 +140,7 @@ static const struct x86_cpu_id pl4_support_ids[] = { { X86_VENDOR_INTEL, 6, INTEL_FAM6_TIGERLAKE_L, X86_FEATURE_ANY }, { X86_VENDOR_INTEL, 6, INTEL_FAM6_ALDERLAKE, X86_FEATURE_ANY }, { X86_VENDOR_INTEL, 6, INTEL_FAM6_ALDERLAKE_L, X86_FEATURE_ANY }, + { X86_VENDOR_INTEL, 6, INTEL_FAM6_RAPTORLAKE, X86_FEATURE_ANY }, {} }; diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 5ef2306fce04..cbe0f96ca342 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1057,6 +1057,16 @@ config REGULATOR_RT5190A buck converters, 1 LDO, mute AC OFF depop function, with the general I2C control interface. +config REGULATOR_RT5759 + tristate "Richtek RT5759 Regulator" + depends on I2C + select REGMAP_I2C + help + This adds support for voltage regulator in Richtek RT5759. + The RT5759 is a high-performance, synchronous step-down DC-DC + converter that can deliver up to 9A output current from 3V to 6.5V + input supply. + config REGULATOR_RT6160 tristate "Richtek RT6160 BuckBoost voltage regulator" depends on I2C @@ -1157,6 +1167,13 @@ config REGULATOR_SLG51000 The SLG51000 is seven compact and customizable low dropout regulators. +config REGULATOR_SM5703 + tristate "Silicon Mitus SM5703 regulators" + depends on MFD_SM5703 + help + This driver provides support for voltage regulators of SM5703 + multi-function device. + config REGULATOR_STM32_BOOSTER tristate "STMicroelectronics STM32 BOOSTER" depends on ARCH_STM32 || COMPILE_TEST diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 1b64ad5767be..8d3ee8b6d41d 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -127,6 +127,7 @@ obj-$(CONFIG_REGULATOR_RT4801) += rt4801-regulator.o obj-$(CONFIG_REGULATOR_RT4831) += rt4831-regulator.o obj-$(CONFIG_REGULATOR_RT5033) += rt5033-regulator.o obj-$(CONFIG_REGULATOR_RT5190A) += rt5190a-regulator.o +obj-$(CONFIG_REGULATOR_RT5759) += rt5759-regulator.o obj-$(CONFIG_REGULATOR_RT6160) += rt6160-regulator.o obj-$(CONFIG_REGULATOR_RT6245) += rt6245-regulator.o obj-$(CONFIG_REGULATOR_RTMV20) += rtmv20-regulator.o @@ -138,6 +139,7 @@ obj-$(CONFIG_REGULATOR_S5M8767) += s5m8767.o obj-$(CONFIG_REGULATOR_SC2731) += sc2731-regulator.o obj-$(CONFIG_REGULATOR_SKY81452) += sky81452-regulator.o obj-$(CONFIG_REGULATOR_SLG51000) += slg51000-regulator.o +obj-$(CONFIG_REGULATOR_SM5703) += sm5703-regulator.o obj-$(CONFIG_REGULATOR_STM32_BOOSTER) += stm32-booster.o obj-$(CONFIG_REGULATOR_STM32_VREFBUF) += stm32-vrefbuf.o obj-$(CONFIG_REGULATOR_STM32_PWR) += stm32-pwr.o diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index d2553970a67b..1e54a833f2cf 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -83,6 +83,7 @@ struct regulator_supply_alias { static int _regulator_is_enabled(struct regulator_dev *rdev); static int _regulator_disable(struct regulator *regulator); +static int _regulator_get_error_flags(struct regulator_dev *rdev, unsigned int *flags); static int _regulator_get_current_limit(struct regulator_dev *rdev); static unsigned int _regulator_get_mode(struct regulator_dev *rdev); static int _notifier_call_chain(struct regulator_dev *rdev, @@ -911,6 +912,30 @@ static ssize_t bypass_show(struct device *dev, } static DEVICE_ATTR_RO(bypass); +#define REGULATOR_ERROR_ATTR(name, bit) \ + static ssize_t name##_show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ + { \ + int ret; \ + unsigned int flags; \ + struct regulator_dev *rdev = dev_get_drvdata(dev); \ + ret = _regulator_get_error_flags(rdev, &flags); \ + if (ret) \ + return ret; \ + return sysfs_emit(buf, "%d\n", !!(flags & (bit))); \ + } \ + static DEVICE_ATTR_RO(name) + +REGULATOR_ERROR_ATTR(under_voltage, REGULATOR_ERROR_UNDER_VOLTAGE); +REGULATOR_ERROR_ATTR(over_current, REGULATOR_ERROR_OVER_CURRENT); +REGULATOR_ERROR_ATTR(regulation_out, REGULATOR_ERROR_REGULATION_OUT); +REGULATOR_ERROR_ATTR(fail, REGULATOR_ERROR_FAIL); +REGULATOR_ERROR_ATTR(over_temp, REGULATOR_ERROR_OVER_TEMP); +REGULATOR_ERROR_ATTR(under_voltage_warn, REGULATOR_ERROR_UNDER_VOLTAGE_WARN); +REGULATOR_ERROR_ATTR(over_current_warn, REGULATOR_ERROR_OVER_CURRENT_WARN); +REGULATOR_ERROR_ATTR(over_voltage_warn, REGULATOR_ERROR_OVER_VOLTAGE_WARN); +REGULATOR_ERROR_ATTR(over_temp_warn, REGULATOR_ERROR_OVER_TEMP_WARN); + /* Calculate the new optimum regulator operating mode based on the new total * consumer load. All locks held by caller */ @@ -1522,6 +1547,24 @@ static int set_machine_constraints(struct regulator_dev *rdev) } } + /* + * If there is no mechanism for controlling the regulator then + * flag it as always_on so we don't end up duplicating checks + * for this so much. Note that we could control the state of + * a supply to control the output on a regulator that has no + * direct control. + */ + if (!rdev->ena_pin && !ops->enable) { + if (rdev->supply_name && !rdev->supply) + return -EPROBE_DEFER; + + if (rdev->supply) + rdev->constraints->always_on = + rdev->supply->rdev->constraints->always_on; + else + rdev->constraints->always_on = true; + } + /* If the constraints say the regulator should be on at this point * and we have control then make sure it is enabled. */ @@ -2133,10 +2176,13 @@ struct regulator *_regulator_get(struct device *dev, const char *id, rdev->exclusive = 1; ret = _regulator_is_enabled(rdev); - if (ret > 0) + if (ret > 0) { rdev->use_count = 1; - else + regulator->enable_count = 1; + } else { rdev->use_count = 0; + regulator->enable_count = 0; + } } link = device_link_add(dev, &rdev->dev, DL_FLAG_STATELESS); @@ -2511,17 +2557,17 @@ static int regulator_ena_gpio_ctrl(struct regulator_dev *rdev, bool enable) } /** - * _regulator_enable_delay - a delay helper function + * _regulator_delay_helper - a delay helper function * @delay: time to delay in microseconds * * Delay for the requested amount of time as per the guidelines in: * * Documentation/timers/timers-howto.rst * - * The assumption here is that regulators will never be enabled in + * The assumption here is that these regulator operations will never used in * atomic context and therefore sleeping functions can be used. */ -static void _regulator_enable_delay(unsigned int delay) +static void _regulator_delay_helper(unsigned int delay) { unsigned int ms = delay / 1000; unsigned int us = delay % 1000; @@ -2603,7 +2649,7 @@ static int _regulator_do_enable(struct regulator_dev *rdev) s64 remaining = ktime_us_delta(end, ktime_get()); if (remaining > 0) - _regulator_enable_delay(remaining); + _regulator_delay_helper(remaining); } if (rdev->ena_pin) { @@ -2630,14 +2676,14 @@ static int _regulator_do_enable(struct regulator_dev *rdev) /* If poll_enabled_time is set, poll upto the delay calculated * above, delaying poll_enabled_time uS to check if the regulator * actually got enabled. - * If the regulator isn't enabled after enable_delay has - * expired, return -ETIMEDOUT. + * If the regulator isn't enabled after our delay helper has expired, + * return -ETIMEDOUT. */ if (rdev->desc->poll_enabled_time) { unsigned int time_remaining = delay; while (time_remaining > 0) { - _regulator_enable_delay(rdev->desc->poll_enabled_time); + _regulator_delay_helper(rdev->desc->poll_enabled_time); if (rdev->desc->ops->get_status) { ret = _regulator_check_status_enabled(rdev); @@ -2656,7 +2702,7 @@ static int _regulator_do_enable(struct regulator_dev *rdev) return -ETIMEDOUT; } } else { - _regulator_enable_delay(delay); + _regulator_delay_helper(delay); } trace_regulator_enable_complete(rdev_get_name(rdev)); @@ -3548,12 +3594,7 @@ static int _regulator_do_set_voltage(struct regulator_dev *rdev, } /* Insert any necessary delays */ - if (delay >= 1000) { - mdelay(delay / 1000); - udelay(delay % 1000); - } else if (delay) { - udelay(delay); - } + _regulator_delay_helper(delay); if (best_val >= 0) { unsigned long data = best_val; @@ -4971,6 +5012,15 @@ static struct attribute *regulator_dev_attrs[] = { &dev_attr_max_microvolts.attr, &dev_attr_min_microamps.attr, &dev_attr_max_microamps.attr, + &dev_attr_under_voltage.attr, + &dev_attr_over_current.attr, + &dev_attr_regulation_out.attr, + &dev_attr_fail.attr, + &dev_attr_over_temp.attr, + &dev_attr_under_voltage_warn.attr, + &dev_attr_over_current_warn.attr, + &dev_attr_over_voltage_warn.attr, + &dev_attr_over_temp_warn.attr, &dev_attr_suspend_standby_state.attr, &dev_attr_suspend_mem_state.attr, &dev_attr_suspend_disk_state.attr, @@ -5026,6 +5076,17 @@ static umode_t regulator_attr_is_visible(struct kobject *kobj, if (attr == &dev_attr_bypass.attr) return ops->get_bypass ? mode : 0; + if (attr == &dev_attr_under_voltage.attr || + attr == &dev_attr_over_current.attr || + attr == &dev_attr_regulation_out.attr || + attr == &dev_attr_fail.attr || + attr == &dev_attr_over_temp.attr || + attr == &dev_attr_under_voltage_warn.attr || + attr == &dev_attr_over_current_warn.attr || + attr == &dev_attr_over_voltage_warn.attr || + attr == &dev_attr_over_temp_warn.attr) + return ops->get_error_flags ? mode : 0; + /* constraints need specific supporting methods */ if (attr == &dev_attr_min_microvolts.attr || attr == &dev_attr_max_microvolts.attr) diff --git a/drivers/regulator/da9121-regulator.c b/drivers/regulator/da9121-regulator.c index eb9df485bd8a..76e0e23bf598 100644 --- a/drivers/regulator/da9121-regulator.c +++ b/drivers/regulator/da9121-regulator.c @@ -1030,6 +1030,8 @@ static int da9121_assign_chip_model(struct i2c_client *i2c, chip->variant_id = DA9121_TYPE_DA9142; regmap = &da9121_2ch_regmap_config; break; + default: + return -EINVAL; } /* Set these up for of_regulator_match call which may want .of_map_modes */ diff --git a/drivers/regulator/fixed.c b/drivers/regulator/fixed.c index 599ad201dca7..2a9867abba20 100644 --- a/drivers/regulator/fixed.c +++ b/drivers/regulator/fixed.c @@ -236,11 +236,8 @@ static int reg_fixed_voltage_probe(struct platform_device *pdev) drvdata->desc.supply_name = devm_kstrdup(&pdev->dev, config->input_supply, GFP_KERNEL); - if (!drvdata->desc.supply_name) { - dev_err(&pdev->dev, - "Failed to allocate input supply\n"); + if (!drvdata->desc.supply_name) return -ENOMEM; - } } if (config->microvolts) diff --git a/drivers/regulator/mt6358-regulator.c b/drivers/regulator/mt6358-regulator.c index eb8027813b99..8a5ce990f1bf 100644 --- a/drivers/regulator/mt6358-regulator.c +++ b/drivers/regulator/mt6358-regulator.c @@ -130,6 +130,102 @@ struct mt6358_regulator_info { .qi = BIT(15), \ } +#define MT6366_BUCK(match, vreg, min, max, step, \ + volt_ranges, vosel_mask, _da_vsel_reg, _da_vsel_mask, \ + _modeset_reg, _modeset_shift) \ +[MT6366_ID_##vreg] = { \ + .desc = { \ + .name = #vreg, \ + .of_match = of_match_ptr(match), \ + .ops = &mt6358_volt_range_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MT6366_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = ((max) - (min)) / (step) + 1, \ + .linear_ranges = volt_ranges, \ + .n_linear_ranges = ARRAY_SIZE(volt_ranges), \ + .vsel_reg = MT6358_BUCK_##vreg##_ELR0, \ + .vsel_mask = vosel_mask, \ + .enable_reg = MT6358_BUCK_##vreg##_CON0, \ + .enable_mask = BIT(0), \ + .of_map_mode = mt6358_map_mode, \ + }, \ + .status_reg = MT6358_BUCK_##vreg##_DBG1, \ + .qi = BIT(0), \ + .da_vsel_reg = _da_vsel_reg, \ + .da_vsel_mask = _da_vsel_mask, \ + .modeset_reg = _modeset_reg, \ + .modeset_mask = BIT(_modeset_shift), \ +} + +#define MT6366_LDO(match, vreg, ldo_volt_table, \ + ldo_index_table, enreg, enbit, vosel, \ + vosel_mask) \ +[MT6366_ID_##vreg] = { \ + .desc = { \ + .name = #vreg, \ + .of_match = of_match_ptr(match), \ + .ops = &mt6358_volt_table_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MT6366_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = ARRAY_SIZE(ldo_volt_table), \ + .volt_table = ldo_volt_table, \ + .vsel_reg = vosel, \ + .vsel_mask = vosel_mask, \ + .enable_reg = enreg, \ + .enable_mask = BIT(enbit), \ + }, \ + .status_reg = MT6358_LDO_##vreg##_CON1, \ + .qi = BIT(15), \ + .index_table = ldo_index_table, \ + .n_table = ARRAY_SIZE(ldo_index_table), \ +} + +#define MT6366_LDO1(match, vreg, min, max, step, \ + volt_ranges, _da_vsel_reg, _da_vsel_mask, \ + vosel, vosel_mask) \ +[MT6366_ID_##vreg] = { \ + .desc = { \ + .name = #vreg, \ + .of_match = of_match_ptr(match), \ + .ops = &mt6358_volt_range_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MT6366_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = ((max) - (min)) / (step) + 1, \ + .linear_ranges = volt_ranges, \ + .n_linear_ranges = ARRAY_SIZE(volt_ranges), \ + .vsel_reg = vosel, \ + .vsel_mask = vosel_mask, \ + .enable_reg = MT6358_LDO_##vreg##_CON0, \ + .enable_mask = BIT(0), \ + }, \ + .da_vsel_reg = _da_vsel_reg, \ + .da_vsel_mask = _da_vsel_mask, \ + .status_reg = MT6358_LDO_##vreg##_DBG1, \ + .qi = BIT(0), \ +} + +#define MT6366_REG_FIXED(match, vreg, \ + enreg, enbit, volt) \ +[MT6366_ID_##vreg] = { \ + .desc = { \ + .name = #vreg, \ + .of_match = of_match_ptr(match), \ + .ops = &mt6358_volt_fixed_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MT6366_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = 1, \ + .enable_reg = enreg, \ + .enable_mask = BIT(enbit), \ + .min_uV = volt, \ + }, \ + .status_reg = MT6358_LDO_##vreg##_CON1, \ + .qi = BIT(15), \ +} + static const struct linear_range buck_volt_range1[] = { REGULATOR_LINEAR_RANGE(500000, 0, 0x7f, 6250), }; @@ -409,6 +505,9 @@ static struct mt6358_regulator_info mt6358_regulators[] = { MT6358_BUCK("buck_vcore", VCORE, 500000, 1293750, 6250, buck_volt_range1, 0x7f, MT6358_BUCK_VCORE_DBG0, 0x7f, MT6358_VCORE_VGPU_ANA_CON0, 1), + MT6358_BUCK("buck_vcore_sshub", VCORE_SSHUB, 500000, 1293750, 6250, + buck_volt_range1, 0x7f, MT6358_BUCK_VCORE_SSHUB_ELR0, 0x7f, + MT6358_VCORE_VGPU_ANA_CON0, 1), MT6358_BUCK("buck_vpa", VPA, 500000, 3650000, 50000, buck_volt_range3, 0x3f, MT6358_BUCK_VPA_DBG0, 0x3f, MT6358_VPA_ANA_CON0, 3), @@ -488,6 +587,10 @@ static struct mt6358_regulator_info mt6358_regulators[] = { MT6358_LDO1("ldo_vsram_others", VSRAM_OTHERS, 500000, 1293750, 6250, buck_volt_range1, MT6358_LDO_VSRAM_OTHERS_DBG0, 0x7f00, MT6358_LDO_VSRAM_CON2, 0x7f), + MT6358_LDO1("ldo_vsram_others_sshub", VSRAM_OTHERS_SSHUB, 500000, + 1293750, 6250, buck_volt_range1, + MT6358_LDO_VSRAM_OTHERS_SSHUB_CON1, 0x7f, + MT6358_LDO_VSRAM_OTHERS_SSHUB_CON1, 0x7f), MT6358_LDO1("ldo_vsram_gpu", VSRAM_GPU, 500000, 1293750, 6250, buck_volt_range1, MT6358_LDO_VSRAM_GPU_DBG0, 0x7f00, MT6358_LDO_VSRAM_CON3, 0x7f), @@ -496,24 +599,124 @@ static struct mt6358_regulator_info mt6358_regulators[] = { MT6358_LDO_VSRAM_CON1, 0x7f), }; +/* The array is indexed by id(MT6366_ID_XXX) */ +static struct mt6358_regulator_info mt6366_regulators[] = { + MT6366_BUCK("buck_vdram1", VDRAM1, 500000, 2087500, 12500, + buck_volt_range2, 0x7f, MT6358_BUCK_VDRAM1_DBG0, 0x7f, + MT6358_VDRAM1_ANA_CON0, 8), + MT6366_BUCK("buck_vcore", VCORE, 500000, 1293750, 6250, + buck_volt_range1, 0x7f, MT6358_BUCK_VCORE_DBG0, 0x7f, + MT6358_VCORE_VGPU_ANA_CON0, 1), + MT6366_BUCK("buck_vcore_sshub", VCORE_SSHUB, 500000, 1293750, 6250, + buck_volt_range1, 0x7f, MT6358_BUCK_VCORE_SSHUB_ELR0, 0x7f, + MT6358_VCORE_VGPU_ANA_CON0, 1), + MT6366_BUCK("buck_vpa", VPA, 500000, 3650000, 50000, + buck_volt_range3, 0x3f, MT6358_BUCK_VPA_DBG0, 0x3f, + MT6358_VPA_ANA_CON0, 3), + MT6366_BUCK("buck_vproc11", VPROC11, 500000, 1293750, 6250, + buck_volt_range1, 0x7f, MT6358_BUCK_VPROC11_DBG0, 0x7f, + MT6358_VPROC_ANA_CON0, 1), + MT6366_BUCK("buck_vproc12", VPROC12, 500000, 1293750, 6250, + buck_volt_range1, 0x7f, MT6358_BUCK_VPROC12_DBG0, 0x7f, + MT6358_VPROC_ANA_CON0, 2), + MT6366_BUCK("buck_vgpu", VGPU, 500000, 1293750, 6250, + buck_volt_range1, 0x7f, MT6358_BUCK_VGPU_ELR0, 0x7f, + MT6358_VCORE_VGPU_ANA_CON0, 2), + MT6366_BUCK("buck_vs2", VS2, 500000, 2087500, 12500, + buck_volt_range2, 0x7f, MT6358_BUCK_VS2_DBG0, 0x7f, + MT6358_VS2_ANA_CON0, 8), + MT6366_BUCK("buck_vmodem", VMODEM, 500000, 1293750, 6250, + buck_volt_range1, 0x7f, MT6358_BUCK_VMODEM_DBG0, 0x7f, + MT6358_VMODEM_ANA_CON0, 8), + MT6366_BUCK("buck_vs1", VS1, 1000000, 2587500, 12500, + buck_volt_range4, 0x7f, MT6358_BUCK_VS1_DBG0, 0x7f, + MT6358_VS1_ANA_CON0, 8), + MT6366_REG_FIXED("ldo_vrf12", VRF12, + MT6358_LDO_VRF12_CON0, 0, 1200000), + MT6366_REG_FIXED("ldo_vio18", VIO18, + MT6358_LDO_VIO18_CON0, 0, 1800000), + MT6366_REG_FIXED("ldo_vcn18", VCN18, MT6358_LDO_VCN18_CON0, 0, 1800000), + MT6366_REG_FIXED("ldo_vfe28", VFE28, MT6358_LDO_VFE28_CON0, 0, 2800000), + MT6366_REG_FIXED("ldo_vcn28", VCN28, MT6358_LDO_VCN28_CON0, 0, 2800000), + MT6366_REG_FIXED("ldo_vxo22", VXO22, MT6358_LDO_VXO22_CON0, 0, 2200000), + MT6366_REG_FIXED("ldo_vaux18", VAUX18, + MT6358_LDO_VAUX18_CON0, 0, 1800000), + MT6366_REG_FIXED("ldo_vbif28", VBIF28, + MT6358_LDO_VBIF28_CON0, 0, 2800000), + MT6366_REG_FIXED("ldo_vio28", VIO28, MT6358_LDO_VIO28_CON0, 0, 2800000), + MT6366_REG_FIXED("ldo_va12", VA12, MT6358_LDO_VA12_CON0, 0, 1200000), + MT6366_REG_FIXED("ldo_vrf18", VRF18, MT6358_LDO_VRF18_CON0, 0, 1800000), + MT6366_REG_FIXED("ldo_vaud28", VAUD28, + MT6358_LDO_VAUD28_CON0, 0, 2800000), + MT6366_LDO("ldo_vdram2", VDRAM2, vdram2_voltages, vdram2_idx, + MT6358_LDO_VDRAM2_CON0, 0, MT6358_LDO_VDRAM2_ELR0, 0x10), + MT6366_LDO("ldo_vsim1", VSIM1, vsim_voltages, vsim_idx, + MT6358_LDO_VSIM1_CON0, 0, MT6358_VSIM1_ANA_CON0, 0xf00), + MT6366_LDO("ldo_vibr", VIBR, vibr_voltages, vibr_idx, + MT6358_LDO_VIBR_CON0, 0, MT6358_VIBR_ANA_CON0, 0xf00), + MT6366_LDO("ldo_vusb", VUSB, vusb_voltages, vusb_idx, + MT6358_LDO_VUSB_CON0_0, 0, MT6358_VUSB_ANA_CON0, 0x700), + MT6366_LDO("ldo_vefuse", VEFUSE, vefuse_voltages, vefuse_idx, + MT6358_LDO_VEFUSE_CON0, 0, MT6358_VEFUSE_ANA_CON0, 0xf00), + MT6366_LDO("ldo_vmch", VMCH, vmch_vemc_voltages, vmch_vemc_idx, + MT6358_LDO_VMCH_CON0, 0, MT6358_VMCH_ANA_CON0, 0x700), + MT6366_LDO("ldo_vemc", VEMC, vmch_vemc_voltages, vmch_vemc_idx, + MT6358_LDO_VEMC_CON0, 0, MT6358_VEMC_ANA_CON0, 0x700), + MT6366_LDO("ldo_vcn33_bt", VCN33_BT, vcn33_bt_wifi_voltages, + vcn33_bt_wifi_idx, MT6358_LDO_VCN33_CON0_0, + 0, MT6358_VCN33_ANA_CON0, 0x300), + MT6366_LDO("ldo_vcn33_wifi", VCN33_WIFI, vcn33_bt_wifi_voltages, + vcn33_bt_wifi_idx, MT6358_LDO_VCN33_CON0_1, + 0, MT6358_VCN33_ANA_CON0, 0x300), + MT6366_LDO("ldo_vmc", VMC, vmc_voltages, vmc_idx, + MT6358_LDO_VMC_CON0, 0, MT6358_VMC_ANA_CON0, 0xf00), + MT6366_LDO("ldo_vsim2", VSIM2, vsim_voltages, vsim_idx, + MT6358_LDO_VSIM2_CON0, 0, MT6358_VSIM2_ANA_CON0, 0xf00), + MT6366_LDO1("ldo_vsram_proc11", VSRAM_PROC11, 500000, 1293750, 6250, + buck_volt_range1, MT6358_LDO_VSRAM_PROC11_DBG0, 0x7f00, + MT6358_LDO_VSRAM_CON0, 0x7f), + MT6366_LDO1("ldo_vsram_others", VSRAM_OTHERS, 500000, 1293750, 6250, + buck_volt_range1, MT6358_LDO_VSRAM_OTHERS_DBG0, 0x7f00, + MT6358_LDO_VSRAM_CON2, 0x7f), + MT6366_LDO1("ldo_vsram_others_sshub", VSRAM_OTHERS_SSHUB, 500000, + 1293750, 6250, buck_volt_range1, + MT6358_LDO_VSRAM_OTHERS_SSHUB_CON1, 0x7f, + MT6358_LDO_VSRAM_OTHERS_SSHUB_CON1, 0x7f), + MT6366_LDO1("ldo_vsram_gpu", VSRAM_GPU, 500000, 1293750, 6250, + buck_volt_range1, MT6358_LDO_VSRAM_GPU_DBG0, 0x7f00, + MT6358_LDO_VSRAM_CON3, 0x7f), + MT6366_LDO1("ldo_vsram_proc12", VSRAM_PROC12, 500000, 1293750, 6250, + buck_volt_range1, MT6358_LDO_VSRAM_PROC12_DBG0, 0x7f00, + MT6358_LDO_VSRAM_CON1, 0x7f), +}; + static int mt6358_regulator_probe(struct platform_device *pdev) { struct mt6397_chip *mt6397 = dev_get_drvdata(pdev->dev.parent); struct regulator_config config = {}; struct regulator_dev *rdev; - int i; + struct mt6358_regulator_info *mt6358_info; + int i, max_regulator; - for (i = 0; i < MT6358_MAX_REGULATOR; i++) { + if (mt6397->chip_id == MT6366_CHIP_ID) { + max_regulator = MT6366_MAX_REGULATOR; + mt6358_info = mt6366_regulators; + } else { + max_regulator = MT6358_MAX_REGULATOR; + mt6358_info = mt6358_regulators; + } + + for (i = 0; i < max_regulator; i++) { config.dev = &pdev->dev; - config.driver_data = &mt6358_regulators[i]; + config.driver_data = &mt6358_info[i]; config.regmap = mt6397->regmap; rdev = devm_regulator_register(&pdev->dev, - &mt6358_regulators[i].desc, + &mt6358_info[i].desc, &config); if (IS_ERR(rdev)) { dev_err(&pdev->dev, "failed to register %s\n", - mt6358_regulators[i].desc.name); + mt6358_info[i].desc.name); return PTR_ERR(rdev); } } diff --git a/drivers/regulator/pca9450-regulator.c b/drivers/regulator/pca9450-regulator.c index 64e5f5f0cc84..14b7d3376516 100644 --- a/drivers/regulator/pca9450-regulator.c +++ b/drivers/regulator/pca9450-regulator.c @@ -174,6 +174,14 @@ static int buck_set_dvs(const struct regulator_desc *desc, } } + if (ret == 0) { + struct pca9450_regulator_desc *regulator = container_of(desc, + struct pca9450_regulator_desc, desc); + + /* Enable DVS control through PMIC_STBY_REQ for this BUCK */ + ret = regmap_update_bits(regmap, regulator->desc.enable_reg, + BUCK1_DVS_CTRL, BUCK1_DVS_CTRL); + } return ret; } @@ -702,6 +710,7 @@ static int pca9450_i2c_probe(struct i2c_client *i2c, struct regulator_config config = { }; struct pca9450 *pca9450; unsigned int device_id, i; + unsigned int reset_ctrl; int ret; if (!i2c->irq) { @@ -802,14 +811,30 @@ static int pca9450_i2c_probe(struct i2c_client *i2c, return ret; } + if (of_property_read_bool(i2c->dev.of_node, "nxp,wdog_b-warm-reset")) + reset_ctrl = WDOG_B_CFG_WARM; + else + reset_ctrl = WDOG_B_CFG_COLD_LDO12; + /* Set reset behavior on assertion of WDOG_B signal */ ret = regmap_update_bits(pca9450->regmap, PCA9450_REG_RESET_CTRL, - WDOG_B_CFG_MASK, WDOG_B_CFG_COLD_LDO12); + WDOG_B_CFG_MASK, reset_ctrl); if (ret) { dev_err(&i2c->dev, "Failed to set WDOG_B reset behavior\n"); return ret; } + if (of_property_read_bool(i2c->dev.of_node, "nxp,i2c-lt-enable")) { + /* Enable I2C Level Translator */ + ret = regmap_update_bits(pca9450->regmap, PCA9450_REG_CONFIG2, + I2C_LT_MASK, I2C_LT_ON_STANDBY_RUN); + if (ret) { + dev_err(&i2c->dev, + "Failed to enable I2C level translator\n"); + return ret; + } + } + /* * The driver uses the LDO5CTRL_H register to control the LDO5 regulator. * This is only valid if the SD_VSEL input of the PMIC is high. Let's diff --git a/drivers/regulator/pfuze100-regulator.c b/drivers/regulator/pfuze100-regulator.c index d60d7d1b7fa2..aa55cfca9e40 100644 --- a/drivers/regulator/pfuze100-regulator.c +++ b/drivers/regulator/pfuze100-regulator.c @@ -521,6 +521,7 @@ static int pfuze_parse_regulators_dt(struct pfuze_chip *chip) parent = of_get_child_by_name(np, "regulators"); if (!parent) { dev_err(dev, "regulators node not found\n"); + of_node_put(np); return -EINVAL; } @@ -550,6 +551,7 @@ static int pfuze_parse_regulators_dt(struct pfuze_chip *chip) } of_node_put(parent); + of_node_put(np); if (ret < 0) { dev_err(dev, "Error parsing regulator init data: %d\n", ret); diff --git a/drivers/regulator/qcom_smd-regulator.c b/drivers/regulator/qcom_smd-regulator.c index 8490aa8eecb1..7dff94a2eb7e 100644 --- a/drivers/regulator/qcom_smd-regulator.c +++ b/drivers/regulator/qcom_smd-regulator.c @@ -944,32 +944,31 @@ static const struct rpm_regulator_data rpm_pm8950_regulators[] = { { "s2", QCOM_SMD_RPM_SMPA, 2, &pm8950_hfsmps, "vdd_s2" }, { "s3", QCOM_SMD_RPM_SMPA, 3, &pm8950_hfsmps, "vdd_s3" }, { "s4", QCOM_SMD_RPM_SMPA, 4, &pm8950_hfsmps, "vdd_s4" }, - { "s5", QCOM_SMD_RPM_SMPA, 5, &pm8950_ftsmps2p5, "vdd_s5" }, + /* S5 is managed via SPMI. */ { "s6", QCOM_SMD_RPM_SMPA, 6, &pm8950_hfsmps, "vdd_s6" }, { "l1", QCOM_SMD_RPM_LDOA, 1, &pm8950_ult_nldo, "vdd_l1_l19" }, { "l2", QCOM_SMD_RPM_LDOA, 2, &pm8950_ult_nldo, "vdd_l2_l23" }, { "l3", QCOM_SMD_RPM_LDOA, 3, &pm8950_ult_nldo, "vdd_l3" }, - { "l4", QCOM_SMD_RPM_LDOA, 4, &pm8950_ult_pldo, "vdd_l4_l5_l6_l7_l16" }, - { "l5", QCOM_SMD_RPM_LDOA, 5, &pm8950_pldo_lv, "vdd_l4_l5_l6_l7_l16" }, - { "l6", QCOM_SMD_RPM_LDOA, 6, &pm8950_pldo_lv, "vdd_l4_l5_l6_l7_l16" }, - { "l7", QCOM_SMD_RPM_LDOA, 7, &pm8950_pldo_lv, "vdd_l4_l5_l6_l7_l16" }, + /* L4 seems not to exist. */ + { "l5", QCOM_SMD_RPM_LDOA, 5, &pm8950_pldo_lv, "vdd_l5_l6_l7_l16" }, + { "l6", QCOM_SMD_RPM_LDOA, 6, &pm8950_pldo_lv, "vdd_l5_l6_l7_l16" }, + { "l7", QCOM_SMD_RPM_LDOA, 7, &pm8950_pldo_lv, "vdd_l5_l6_l7_l16" }, { "l8", QCOM_SMD_RPM_LDOA, 8, &pm8950_ult_pldo, "vdd_l8_l11_l12_l17_l22" }, { "l9", QCOM_SMD_RPM_LDOA, 9, &pm8950_ult_pldo, "vdd_l9_l10_l13_l14_l15_l18" }, { "l10", QCOM_SMD_RPM_LDOA, 10, &pm8950_ult_nldo, "vdd_l9_l10_l13_l14_l15_l18"}, - { "l11", QCOM_SMD_RPM_LDOA, 11, &pm8950_ult_pldo, "vdd_l8_l11_l12_l17_l22"}, - { "l12", QCOM_SMD_RPM_LDOA, 12, &pm8950_ult_pldo, "vdd_l8_l11_l12_l17_l22"}, - { "l13", QCOM_SMD_RPM_LDOA, 13, &pm8950_ult_pldo, "vdd_l9_l10_l13_l14_l15_l18"}, - { "l14", QCOM_SMD_RPM_LDOA, 14, &pm8950_ult_pldo, "vdd_l9_l10_l13_l14_l15_l18"}, - { "l15", QCOM_SMD_RPM_LDOA, 15, &pm8950_ult_pldo, "vdd_l9_l10_l13_l14_l15_l18"}, - { "l16", QCOM_SMD_RPM_LDOA, 16, &pm8950_ult_pldo, "vdd_l4_l5_l6_l7_l16"}, - { "l17", QCOM_SMD_RPM_LDOA, 17, &pm8950_ult_pldo, "vdd_l8_l11_l12_l17_l22"}, - { "l18", QCOM_SMD_RPM_LDOA, 18, &pm8950_ult_pldo, "vdd_l9_l10_l13_l14_l15_l18"}, - { "l19", QCOM_SMD_RPM_LDOA, 18, &pm8950_pldo, "vdd_l1_l19"}, - { "l20", QCOM_SMD_RPM_LDOA, 18, &pm8950_pldo, "vdd_l20"}, - { "l21", QCOM_SMD_RPM_LDOA, 18, &pm8950_pldo, "vdd_l21"}, - { "l22", QCOM_SMD_RPM_LDOA, 18, &pm8950_pldo, "vdd_l8_l11_l12_l17_l22"}, - { "l23", QCOM_SMD_RPM_LDOA, 18, &pm8950_pldo, "vdd_l2_l23"}, + { "l11", QCOM_SMD_RPM_LDOA, 11, &pm8950_ult_pldo, "vdd_l8_l11_l12_l17_l22" }, + { "l12", QCOM_SMD_RPM_LDOA, 12, &pm8950_ult_pldo, "vdd_l8_l11_l12_l17_l22" }, + { "l13", QCOM_SMD_RPM_LDOA, 13, &pm8950_ult_pldo, "vdd_l9_l10_l13_l14_l15_l18" }, + { "l14", QCOM_SMD_RPM_LDOA, 14, &pm8950_ult_pldo, "vdd_l9_l10_l13_l14_l15_l18" }, + { "l15", QCOM_SMD_RPM_LDOA, 15, &pm8950_ult_pldo, "vdd_l9_l10_l13_l14_l15_l18" }, + { "l16", QCOM_SMD_RPM_LDOA, 16, &pm8950_ult_pldo, "vdd_l5_l6_l7_l16" }, + { "l17", QCOM_SMD_RPM_LDOA, 17, &pm8950_ult_pldo, "vdd_l8_l11_l12_l17_l22" }, + /* L18 seems not to exist. */ + { "l19", QCOM_SMD_RPM_LDOA, 19, &pm8950_pldo, "vdd_l1_l19" }, + /* L20 & L21 seem not to exist. */ + { "l22", QCOM_SMD_RPM_LDOA, 22, &pm8950_pldo, "vdd_l8_l11_l12_l17_l22" }, + { "l23", QCOM_SMD_RPM_LDOA, 23, &pm8950_pldo, "vdd_l2_l23" }, {} }; diff --git a/drivers/regulator/rpi-panel-attiny-regulator.c b/drivers/regulator/rpi-panel-attiny-regulator.c index f7df0f4b2f87..fa8706a352ce 100644 --- a/drivers/regulator/rpi-panel-attiny-regulator.c +++ b/drivers/regulator/rpi-panel-attiny-regulator.c @@ -364,7 +364,6 @@ static int attiny_i2c_probe(struct i2c_client *i2c, state->gc.parent = &i2c->dev; state->gc.label = i2c->name; state->gc.owner = THIS_MODULE; - state->gc.of_node = i2c->dev.of_node; state->gc.base = -1; state->gc.ngpio = NUM_GPIO; diff --git a/drivers/regulator/rt4801-regulator.c b/drivers/regulator/rt4801-regulator.c index 7a87788d3f09..563d79196fdd 100644 --- a/drivers/regulator/rt4801-regulator.c +++ b/drivers/regulator/rt4801-regulator.c @@ -29,11 +29,33 @@ struct rt4801_priv { struct device *dev; - struct gpio_descs *enable_gpios; + struct gpio_desc *enable_gpios[DSV_OUT_MAX]; unsigned int enable_flag; unsigned int volt_sel[DSV_OUT_MAX]; }; +static int rt4801_of_parse_cb(struct device_node *np, + const struct regulator_desc *desc, + struct regulator_config *config) +{ + struct rt4801_priv *priv = config->driver_data; + int id = desc->id; + + if (priv->enable_gpios[id]) { + dev_warn(priv->dev, "duplicated enable-gpios property\n"); + return 0; + } + priv->enable_gpios[id] = devm_fwnode_gpiod_get_index(priv->dev, + of_fwnode_handle(np), + "enable", 0, + GPIOD_OUT_HIGH, + "rt4801"); + if (IS_ERR(priv->enable_gpios[id])) + priv->enable_gpios[id] = NULL; + + return 0; +} + static int rt4801_set_voltage_sel(struct regulator_dev *rdev, unsigned int selector) { struct rt4801_priv *priv = rdev_get_drvdata(rdev); @@ -63,15 +85,14 @@ static int rt4801_get_voltage_sel(struct regulator_dev *rdev) static int rt4801_enable(struct regulator_dev *rdev) { struct rt4801_priv *priv = rdev_get_drvdata(rdev); - struct gpio_descs *gpios = priv->enable_gpios; int id = rdev_get_id(rdev), ret; - if (!gpios || gpios->ndescs <= id) { + if (!priv->enable_gpios[id]) { dev_warn(&rdev->dev, "no dedicated gpio can control\n"); goto bypass_gpio; } - gpiod_set_value(gpios->desc[id], 1); + gpiod_set_value(priv->enable_gpios[id], 1); bypass_gpio: ret = regmap_write(rdev->regmap, rdev->desc->vsel_reg, priv->volt_sel[id]); @@ -85,15 +106,14 @@ bypass_gpio: static int rt4801_disable(struct regulator_dev *rdev) { struct rt4801_priv *priv = rdev_get_drvdata(rdev); - struct gpio_descs *gpios = priv->enable_gpios; int id = rdev_get_id(rdev); - if (!gpios || gpios->ndescs <= id) { + if (!priv->enable_gpios[id]) { dev_warn(&rdev->dev, "no dedicated gpio can control\n"); goto bypass_gpio; } - gpiod_set_value(gpios->desc[id], 0); + gpiod_set_value(priv->enable_gpios[id], 0); bypass_gpio: priv->enable_flag &= ~BIT(id); @@ -122,6 +142,7 @@ static const struct regulator_desc rt4801_regulator_descs[] = { .name = "DSVP", .ops = &rt4801_regulator_ops, .of_match = of_match_ptr("DSVP"), + .of_parse_cb = rt4801_of_parse_cb, .type = REGULATOR_VOLTAGE, .id = DSV_OUT_POS, .min_uV = MIN_UV, @@ -135,6 +156,7 @@ static const struct regulator_desc rt4801_regulator_descs[] = { .name = "DSVN", .ops = &rt4801_regulator_ops, .of_match = of_match_ptr("DSVN"), + .of_parse_cb = rt4801_of_parse_cb, .type = REGULATOR_VOLTAGE, .id = DSV_OUT_NEG, .min_uV = MIN_UV, @@ -172,10 +194,15 @@ static int rt4801_probe(struct i2c_client *i2c) return PTR_ERR(regmap); } - priv->enable_gpios = devm_gpiod_get_array_optional(&i2c->dev, "enable", GPIOD_OUT_HIGH); - if (IS_ERR(priv->enable_gpios)) { - dev_err(&i2c->dev, "Failed to get gpios\n"); - return PTR_ERR(priv->enable_gpios); + for (i = 0; i < DSV_OUT_MAX; i++) { + priv->enable_gpios[i] = devm_gpiod_get_index_optional(&i2c->dev, + "enable", + i, + GPIOD_OUT_HIGH); + if (IS_ERR(priv->enable_gpios[i])) { + dev_err(&i2c->dev, "Failed to get gpios\n"); + return PTR_ERR(priv->enable_gpios[i]); + } } for (i = 0; i < DSV_OUT_MAX; i++) { diff --git a/drivers/regulator/rt5759-regulator.c b/drivers/regulator/rt5759-regulator.c new file mode 100644 index 000000000000..6b96899eb27e --- /dev/null +++ b/drivers/regulator/rt5759-regulator.c @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define RT5759_REG_VENDORINFO 0x00 +#define RT5759_REG_FREQ 0x01 +#define RT5759_REG_VSEL 0x02 +#define RT5759_REG_DCDCCTRL 0x03 +#define RT5759_REG_STATUS 0x04 +#define RT5759_REG_DCDCSET 0x05 +#define RT5759A_REG_WDTEN 0x42 + +#define RT5759_TSTEP_MASK GENMASK(3, 2) +#define RT5759_VSEL_MASK GENMASK(6, 0) +#define RT5759_DISCHARGE_MASK BIT(3) +#define RT5759_FPWM_MASK BIT(2) +#define RT5759_ENABLE_MASK BIT(1) +#define RT5759_OT_MASK BIT(1) +#define RT5759_UV_MASK BIT(0) +#define RT5957_OCLVL_MASK GENMASK(7, 6) +#define RT5759_OCLVL_SHIFT 6 +#define RT5957_OTLVL_MASK GENMASK(5, 4) +#define RT5759_OTLVL_SHIFT 4 +#define RT5759A_WDTEN_MASK BIT(1) + +#define RT5759_MANUFACTURER_ID 0x82 +/* vsel range 0x00 ~ 0x5A */ +#define RT5759_NUM_VOLTS 91 +#define RT5759_MIN_UV 600000 +#define RT5759_STEP_UV 10000 +#define RT5759A_STEP_UV 12500 +#define RT5759_MINSS_TIMEUS 1500 + +#define RT5759_PSKIP_MODE 0 +#define RT5759_FPWM_MODE 1 + +enum { + CHIP_TYPE_RT5759 = 0, + CHIP_TYPE_RT5759A, + CHIP_TYPE_MAX +}; + +struct rt5759_priv { + struct device *dev; + struct regmap *regmap; + struct regulator_desc desc; + unsigned long chip_type; +}; + +static int rt5759_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct regmap *regmap = rdev_get_regmap(rdev); + unsigned int mode_val; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + mode_val = 0; + break; + case REGULATOR_MODE_FAST: + mode_val = RT5759_FPWM_MASK; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(regmap, RT5759_REG_STATUS, RT5759_FPWM_MASK, + mode_val); +} + +static unsigned int rt5759_get_mode(struct regulator_dev *rdev) +{ + struct regmap *regmap = rdev_get_regmap(rdev); + unsigned int regval; + int ret; + + ret = regmap_read(regmap, RT5759_REG_DCDCCTRL, ®val); + if (ret) + return REGULATOR_MODE_INVALID; + + if (regval & RT5759_FPWM_MASK) + return REGULATOR_MODE_FAST; + + return REGULATOR_MODE_NORMAL; +} + +static int rt5759_get_error_flags(struct regulator_dev *rdev, + unsigned int *flags) +{ + struct regmap *regmap = rdev_get_regmap(rdev); + unsigned int status, events = 0; + int ret; + + ret = regmap_read(regmap, RT5759_REG_STATUS, &status); + if (ret) + return ret; + + if (status & RT5759_OT_MASK) + events |= REGULATOR_ERROR_OVER_TEMP; + + if (status & RT5759_UV_MASK) + events |= REGULATOR_ERROR_UNDER_VOLTAGE; + + *flags = events; + return 0; +} + +static int rt5759_set_ocp(struct regulator_dev *rdev, int lim_uA, int severity, + bool enable) +{ + struct regmap *regmap = rdev_get_regmap(rdev); + int ocp_lvl[] = { 9800000, 10800000, 11800000 }; + unsigned int ocp_regval; + int i; + + /* Only support over current protection parameter */ + if (severity != REGULATOR_SEVERITY_PROT) + return 0; + + if (enable) { + /* Default ocp level is 10.8A */ + if (lim_uA == 0) + lim_uA = 10800000; + + for (i = 0; i < ARRAY_SIZE(ocp_lvl); i++) { + if (lim_uA <= ocp_lvl[i]) + break; + } + + if (i == ARRAY_SIZE(ocp_lvl)) + i = ARRAY_SIZE(ocp_lvl) - 1; + + ocp_regval = i + 1; + } else + ocp_regval = 0; + + return regmap_update_bits(regmap, RT5759_REG_DCDCSET, RT5957_OCLVL_MASK, + ocp_regval << RT5759_OCLVL_SHIFT); +} + +static int rt5759_set_otp(struct regulator_dev *rdev, int lim, int severity, + bool enable) +{ + struct regmap *regmap = rdev_get_regmap(rdev); + int otp_lvl[] = { 140, 150, 170 }; + unsigned int otp_regval; + int i; + + /* Only support over temperature protection parameter */ + if (severity != REGULATOR_SEVERITY_PROT) + return 0; + + if (enable) { + /* Default otp level is 150'c */ + if (lim == 0) + lim = 150; + + for (i = 0; i < ARRAY_SIZE(otp_lvl); i++) { + if (lim <= otp_lvl[i]) + break; + } + + if (i == ARRAY_SIZE(otp_lvl)) + i = ARRAY_SIZE(otp_lvl) - 1; + + otp_regval = i + 1; + } else + otp_regval = 0; + + return regmap_update_bits(regmap, RT5759_REG_DCDCSET, RT5957_OTLVL_MASK, + otp_regval << RT5759_OTLVL_SHIFT); +} + +static const struct regulator_ops rt5759_regulator_ops = { + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_active_discharge = regulator_set_active_discharge_regmap, + .set_mode = rt5759_set_mode, + .get_mode = rt5759_get_mode, + .set_ramp_delay = regulator_set_ramp_delay_regmap, + .get_error_flags = rt5759_get_error_flags, + .set_over_current_protection = rt5759_set_ocp, + .set_thermal_protection = rt5759_set_otp, +}; + +static unsigned int rt5759_of_map_mode(unsigned int mode) +{ + switch (mode) { + case RT5759_FPWM_MODE: + return REGULATOR_MODE_FAST; + case RT5759_PSKIP_MODE: + return REGULATOR_MODE_NORMAL; + default: + return REGULATOR_MODE_INVALID; + } +} + +static const unsigned int rt5759_ramp_table[] = { 20000, 15000, 10000, 5000 }; + +static int rt5759_regulator_register(struct rt5759_priv *priv) +{ + struct device_node *np = priv->dev->of_node; + struct regulator_desc *reg_desc = &priv->desc; + struct regulator_config reg_cfg; + struct regulator_dev *rdev; + int ret; + + reg_desc->name = "rt5759-buck"; + reg_desc->type = REGULATOR_VOLTAGE; + reg_desc->owner = THIS_MODULE; + reg_desc->ops = &rt5759_regulator_ops; + reg_desc->n_voltages = RT5759_NUM_VOLTS; + reg_desc->min_uV = RT5759_MIN_UV; + reg_desc->uV_step = RT5759_STEP_UV; + reg_desc->vsel_reg = RT5759_REG_VSEL; + reg_desc->vsel_mask = RT5759_VSEL_MASK; + reg_desc->enable_reg = RT5759_REG_DCDCCTRL; + reg_desc->enable_mask = RT5759_ENABLE_MASK; + reg_desc->active_discharge_reg = RT5759_REG_DCDCCTRL; + reg_desc->active_discharge_mask = RT5759_DISCHARGE_MASK; + reg_desc->active_discharge_on = RT5759_DISCHARGE_MASK; + reg_desc->ramp_reg = RT5759_REG_FREQ; + reg_desc->ramp_mask = RT5759_TSTEP_MASK; + reg_desc->ramp_delay_table = rt5759_ramp_table; + reg_desc->n_ramp_values = ARRAY_SIZE(rt5759_ramp_table); + reg_desc->enable_time = RT5759_MINSS_TIMEUS; + reg_desc->of_map_mode = rt5759_of_map_mode; + + /* + * RT5759 step uV = 10000 + * RT5759A step uV = 12500 + */ + if (priv->chip_type == CHIP_TYPE_RT5759A) + reg_desc->uV_step = RT5759A_STEP_UV; + + reg_cfg.dev = priv->dev; + reg_cfg.of_node = np; + reg_cfg.init_data = of_get_regulator_init_data(priv->dev, np, reg_desc); + reg_cfg.regmap = priv->regmap; + + rdev = devm_regulator_register(priv->dev, reg_desc, ®_cfg); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(priv->dev, "Failed to register regulator (%d)\n", ret); + return ret; + } + + return 0; +} + +static int rt5759_init_device_property(struct rt5759_priv *priv) +{ + unsigned int val = 0; + + /* + * Only RT5759A support external watchdog input + */ + if (priv->chip_type != CHIP_TYPE_RT5759A) + return 0; + + if (device_property_read_bool(priv->dev, "richtek,watchdog-enable")) + val = RT5759A_WDTEN_MASK; + + return regmap_update_bits(priv->regmap, RT5759A_REG_WDTEN, + RT5759A_WDTEN_MASK, val); +} + +static int rt5759_manufacturer_check(struct rt5759_priv *priv) +{ + unsigned int vendor; + int ret; + + ret = regmap_read(priv->regmap, RT5759_REG_VENDORINFO, &vendor); + if (ret) + return ret; + + if (vendor != RT5759_MANUFACTURER_ID) { + dev_err(priv->dev, "vendor info not correct (%d)\n", vendor); + return -EINVAL; + } + + return 0; +} + +static bool rt5759_is_accessible_reg(struct device *dev, unsigned int reg) +{ + struct rt5759_priv *priv = dev_get_drvdata(dev); + + if (reg <= RT5759_REG_DCDCSET) + return true; + + if (priv->chip_type == CHIP_TYPE_RT5759A && reg == RT5759A_REG_WDTEN) + return true; + + return false; +} + +static const struct regmap_config rt5759_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = RT5759A_REG_WDTEN, + .readable_reg = rt5759_is_accessible_reg, + .writeable_reg = rt5759_is_accessible_reg, +}; + +static int rt5759_probe(struct i2c_client *i2c) +{ + struct rt5759_priv *priv; + int ret; + + priv = devm_kzalloc(&i2c->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &i2c->dev; + priv->chip_type = (unsigned long)of_device_get_match_data(&i2c->dev); + i2c_set_clientdata(i2c, priv); + + priv->regmap = devm_regmap_init_i2c(i2c, &rt5759_regmap_config); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + dev_err(&i2c->dev, "Failed to allocate regmap (%d)\n", ret); + return ret; + } + + ret = rt5759_manufacturer_check(priv); + if (ret) { + dev_err(&i2c->dev, "Failed to check device (%d)\n", ret); + return ret; + } + + ret = rt5759_init_device_property(priv); + if (ret) { + dev_err(&i2c->dev, "Failed to init device (%d)\n", ret); + return ret; + } + + return rt5759_regulator_register(priv); +} + +static const struct of_device_id __maybe_unused rt5759_device_table[] = { + { .compatible = "richtek,rt5759", .data = (void *)CHIP_TYPE_RT5759 }, + { .compatible = "richtek,rt5759a", .data = (void *)CHIP_TYPE_RT5759A }, + {} +}; +MODULE_DEVICE_TABLE(of, rt5759_device_table); + +static struct i2c_driver rt5759_driver = { + .driver = { + .name = "rt5759", + .of_match_table = of_match_ptr(rt5759_device_table), + }, + .probe_new = rt5759_probe, +}; +module_i2c_driver(rt5759_driver); + +MODULE_AUTHOR("ChiYuan Huang "); +MODULE_DESCRIPTION("Richtek RT5759 Regulator Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/scmi-regulator.c b/drivers/regulator/scmi-regulator.c index 1f02f60ad136..41ae7ac27ff6 100644 --- a/drivers/regulator/scmi-regulator.c +++ b/drivers/regulator/scmi-regulator.c @@ -352,7 +352,7 @@ static int scmi_regulator_probe(struct scmi_device *sdev) return ret; } } - + of_node_put(np); /* * Register a regulator for each valid regulator-DT-entry that we * can successfully reach via SCMI and has a valid associated voltage diff --git a/drivers/regulator/sm5703-regulator.c b/drivers/regulator/sm5703-regulator.c new file mode 100644 index 000000000000..05ad28fc4da8 --- /dev/null +++ b/drivers/regulator/sm5703-regulator.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include + +enum sm5703_regulators { + SM5703_BUCK, + SM5703_LDO1, + SM5703_LDO2, + SM5703_LDO3, + SM5703_USBLDO1, + SM5703_USBLDO2, + SM5703_VBUS, + SM5703_MAX_REGULATORS, +}; + +static const int sm5703_ldo_voltagemap[] = { + 1500000, 1800000, 2600000, 2800000, 3000000, 3300000, +}; + +static const int sm5703_buck_voltagemap[] = { + 1000000, 1000000, 1000000, 1000000, + 1000000, 1000000, 1000000, 1000000, + 1000000, 1000000, 1000000, 1100000, + 1200000, 1300000, 1400000, 1500000, + 1600000, 1700000, 1800000, 1900000, + 2000000, 2100000, 2200000, 2300000, + 2400000, 2500000, 2600000, 2700000, + 2800000, 2900000, 3000000, 3000000, +}; + +#define SM5703USBLDO(_name, _id) \ + [SM5703_USBLDO ## _id] = { \ + .name = _name, \ + .of_match = _name, \ + .regulators_node = "regulators", \ + .type = REGULATOR_VOLTAGE, \ + .id = SM5703_USBLDO ## _id, \ + .ops = &sm5703_regulator_ops_fixed, \ + .fixed_uV = SM5703_USBLDO_MICROVOLT, \ + .enable_reg = SM5703_REG_USBLDO12, \ + .enable_mask = SM5703_REG_EN_USBLDO ##_id, \ + .owner = THIS_MODULE, \ + } + +#define SM5703VBUS(_name) \ + [SM5703_VBUS] = { \ + .name = _name, \ + .of_match = _name, \ + .regulators_node = "regulators", \ + .type = REGULATOR_VOLTAGE, \ + .id = SM5703_VBUS, \ + .ops = &sm5703_regulator_ops_fixed, \ + .fixed_uV = SM5703_VBUS_MICROVOLT, \ + .enable_reg = SM5703_REG_CNTL, \ + .enable_mask = SM5703_OPERATION_MODE_MASK, \ + .enable_val = SM5703_OPERATION_MODE_USB_OTG_MODE, \ + .disable_val = SM5703_OPERATION_MODE_CHARGING_ON, \ + .owner = THIS_MODULE, \ + } + +#define SM5703BUCK(_name) \ + [SM5703_BUCK] = { \ + .name = _name, \ + .of_match = _name, \ + .regulators_node = "regulators", \ + .type = REGULATOR_VOLTAGE, \ + .id = SM5703_BUCK, \ + .ops = &sm5703_regulator_ops, \ + .n_voltages = ARRAY_SIZE(sm5703_buck_voltagemap), \ + .volt_table = sm5703_buck_voltagemap, \ + .vsel_reg = SM5703_REG_BUCK, \ + .vsel_mask = SM5703_BUCK_VOLT_MASK, \ + .enable_reg = SM5703_REG_BUCK, \ + .enable_mask = SM5703_REG_EN_BUCK, \ + .owner = THIS_MODULE, \ + } + +#define SM5703LDO(_name, _id) \ + [SM5703_LDO ## _id] = { \ + .name = _name, \ + .of_match = _name, \ + .regulators_node = "regulators", \ + .type = REGULATOR_VOLTAGE, \ + .id = SM5703_LDO ## _id, \ + .ops = &sm5703_regulator_ops, \ + .n_voltages = ARRAY_SIZE(sm5703_ldo_voltagemap), \ + .volt_table = sm5703_ldo_voltagemap, \ + .vsel_reg = SM5703_REG_LDO ##_id, \ + .vsel_mask = SM5703_LDO_VOLT_MASK, \ + .enable_reg = SM5703_REG_LDO ##_id, \ + .enable_mask = SM5703_LDO_EN, \ + .owner = THIS_MODULE, \ + } + +static const struct regulator_ops sm5703_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_table, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +static const struct regulator_ops sm5703_regulator_ops_fixed = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static struct regulator_desc sm5703_regulators_desc[SM5703_MAX_REGULATORS] = { + SM5703BUCK("buck"), + SM5703LDO("ldo1", 1), + SM5703LDO("ldo2", 2), + SM5703LDO("ldo3", 3), + SM5703USBLDO("usbldo1", 1), + SM5703USBLDO("usbldo2", 2), + SM5703VBUS("vbus"), +}; + +static int sm5703_regulator_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct regulator_config config = { NULL, }; + struct regulator_dev *rdev; + struct sm5703_dev *sm5703 = dev_get_drvdata(pdev->dev.parent); + int i; + + config.dev = dev->parent; + config.regmap = sm5703->regmap; + + for (i = 0; i < SM5703_MAX_REGULATORS; i++) { + rdev = devm_regulator_register(dev, + &sm5703_regulators_desc[i], + &config); + if (IS_ERR(rdev)) + return dev_err_probe(dev, PTR_ERR(rdev), + "Failed to register a regulator\n"); + } + + return 0; +} + +static const struct platform_device_id sm5703_regulator_id[] = { + { "sm5703-regulator", 0 }, + {} +}; +MODULE_DEVICE_TABLE(platform, sm5703_regulator_id); + +static struct platform_driver sm5703_regulator_driver = { + .driver = { + .name = "sm5703-regulator", + }, + .probe = sm5703_regulator_probe, + .id_table = sm5703_regulator_id, +}; + +module_platform_driver(sm5703_regulator_driver); + +MODULE_DESCRIPTION("Silicon Mitus SM5703 LDO/Buck/USB regulator driver"); +MODULE_AUTHOR("Markuss Broks "); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/stm32-vrefbuf.c b/drivers/regulator/stm32-vrefbuf.c index 161622ea7259..30ea3bc8ca19 100644 --- a/drivers/regulator/stm32-vrefbuf.c +++ b/drivers/regulator/stm32-vrefbuf.c @@ -44,11 +44,9 @@ static int stm32_vrefbuf_enable(struct regulator_dev *rdev) u32 val; int ret; - ret = pm_runtime_get_sync(priv->dev); - if (ret < 0) { - pm_runtime_put_noidle(priv->dev); + ret = pm_runtime_resume_and_get(priv->dev); + if (ret < 0) return ret; - } val = readl_relaxed(priv->base + STM32_VREFBUF_CSR); val = (val & ~STM32_HIZ) | STM32_ENVR; @@ -81,11 +79,9 @@ static int stm32_vrefbuf_disable(struct regulator_dev *rdev) u32 val; int ret; - ret = pm_runtime_get_sync(priv->dev); - if (ret < 0) { - pm_runtime_put_noidle(priv->dev); + ret = pm_runtime_resume_and_get(priv->dev); + if (ret < 0) return ret; - } val = readl_relaxed(priv->base + STM32_VREFBUF_CSR); val &= ~STM32_ENVR; @@ -102,11 +98,9 @@ static int stm32_vrefbuf_is_enabled(struct regulator_dev *rdev) struct stm32_vrefbuf *priv = rdev_get_drvdata(rdev); int ret; - ret = pm_runtime_get_sync(priv->dev); - if (ret < 0) { - pm_runtime_put_noidle(priv->dev); + ret = pm_runtime_resume_and_get(priv->dev); + if (ret < 0) return ret; - } ret = readl_relaxed(priv->base + STM32_VREFBUF_CSR) & STM32_ENVR; @@ -123,11 +117,9 @@ static int stm32_vrefbuf_set_voltage_sel(struct regulator_dev *rdev, u32 val; int ret; - ret = pm_runtime_get_sync(priv->dev); - if (ret < 0) { - pm_runtime_put_noidle(priv->dev); + ret = pm_runtime_resume_and_get(priv->dev); + if (ret < 0) return ret; - } val = readl_relaxed(priv->base + STM32_VREFBUF_CSR); val = (val & ~STM32_VRS) | FIELD_PREP(STM32_VRS, sel); @@ -145,11 +137,9 @@ static int stm32_vrefbuf_get_voltage_sel(struct regulator_dev *rdev) u32 val; int ret; - ret = pm_runtime_get_sync(priv->dev); - if (ret < 0) { - pm_runtime_put_noidle(priv->dev); + ret = pm_runtime_resume_and_get(priv->dev); + if (ret < 0) return ret; - } val = readl_relaxed(priv->base + STM32_VREFBUF_CSR); ret = FIELD_GET(STM32_VRS, val); diff --git a/drivers/soc/rockchip/pm_domains.c b/drivers/soc/rockchip/pm_domains.c index 0868b7d406fb..b1cf7d29dafd 100644 --- a/drivers/soc/rockchip/pm_domains.c +++ b/drivers/soc/rockchip/pm_domains.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -16,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -139,6 +141,109 @@ struct rockchip_pmu { #define DOMAIN_RK3568(name, pwr, req, wakeup) \ DOMAIN_M(name, pwr, pwr, req, req, req, wakeup) +/* + * Dynamic Memory Controller may need to coordinate with us -- see + * rockchip_pmu_block(). + * + * dmc_pmu_mutex protects registration-time races, so DMC driver doesn't try to + * block() while we're initializing the PMU. + */ +static DEFINE_MUTEX(dmc_pmu_mutex); +static struct rockchip_pmu *dmc_pmu; + +/* + * Block PMU transitions and make sure they don't interfere with ARM Trusted + * Firmware operations. There are two conflicts, noted in the comments below. + * + * Caller must unblock PMU transitions via rockchip_pmu_unblock(). + */ +int rockchip_pmu_block(void) +{ + struct rockchip_pmu *pmu; + struct generic_pm_domain *genpd; + struct rockchip_pm_domain *pd; + int i, ret; + + mutex_lock(&dmc_pmu_mutex); + + /* No PMU (yet)? Then we just block rockchip_pmu_probe(). */ + if (!dmc_pmu) + return 0; + pmu = dmc_pmu; + + /* + * mutex blocks all idle transitions: we can't touch the + * PMU_BUS_IDLE_REQ (our ".idle_offset") register while ARM Trusted + * Firmware might be using it. + */ + mutex_lock(&pmu->mutex); + + /* + * Power domain clocks: Per Rockchip, we *must* keep certain clocks + * enabled for the duration of power-domain transitions. Most + * transitions are handled by this driver, but some cases (in + * particular, DRAM DVFS / memory-controller idle) must be handled by + * firmware. Firmware can handle most clock management via a special + * "ungate" register (PMU_CRU_GATEDIS_CON0), but unfortunately, this + * doesn't handle PLLs. We can assist this transition by doing the + * clock management on behalf of firmware. + */ + for (i = 0; i < pmu->genpd_data.num_domains; i++) { + genpd = pmu->genpd_data.domains[i]; + if (genpd) { + pd = to_rockchip_pd(genpd); + ret = clk_bulk_enable(pd->num_clks, pd->clks); + if (ret < 0) { + dev_err(pmu->dev, + "failed to enable clks for domain '%s': %d\n", + genpd->name, ret); + goto err; + } + } + } + + return 0; + +err: + for (i = i - 1; i >= 0; i--) { + genpd = pmu->genpd_data.domains[i]; + if (genpd) { + pd = to_rockchip_pd(genpd); + clk_bulk_disable(pd->num_clks, pd->clks); + } + } + mutex_unlock(&pmu->mutex); + mutex_unlock(&dmc_pmu_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(rockchip_pmu_block); + +/* Unblock PMU transitions. */ +void rockchip_pmu_unblock(void) +{ + struct rockchip_pmu *pmu; + struct generic_pm_domain *genpd; + struct rockchip_pm_domain *pd; + int i; + + if (dmc_pmu) { + pmu = dmc_pmu; + for (i = 0; i < pmu->genpd_data.num_domains; i++) { + genpd = pmu->genpd_data.domains[i]; + if (genpd) { + pd = to_rockchip_pd(genpd); + clk_bulk_disable(pd->num_clks, pd->clks); + } + } + + mutex_unlock(&pmu->mutex); + } + + mutex_unlock(&dmc_pmu_mutex); +} +EXPORT_SYMBOL_GPL(rockchip_pmu_unblock); + static bool rockchip_pmu_domain_is_idle(struct rockchip_pm_domain *pd) { struct rockchip_pmu *pmu = pd->pmu; @@ -690,6 +795,12 @@ static int rockchip_pm_domain_probe(struct platform_device *pdev) error = -ENODEV; + /* + * Prevent any rockchip_pmu_block() from racing with the remainder of + * setup (clocks, register initialization). + */ + mutex_lock(&dmc_pmu_mutex); + for_each_available_child_of_node(np, node) { error = rockchip_pm_add_one_domain(pmu, node); if (error) { @@ -719,10 +830,17 @@ static int rockchip_pm_domain_probe(struct platform_device *pdev) goto err_out; } + /* We only expect one PMU. */ + if (!WARN_ON_ONCE(dmc_pmu)) + dmc_pmu = pmu; + + mutex_unlock(&dmc_pmu_mutex); + return 0; err_out: rockchip_pm_domain_cleanup(pmu); + mutex_unlock(&dmc_pmu_mutex); return error; } diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index d2815eb361c0..38117775ff79 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -101,6 +101,17 @@ config SPI_ARMADA_3700 This enables support for the SPI controller present on the Marvell Armada 3700 SoCs. +config SPI_ASPEED_SMC + tristate "Aspeed flash controllers in SPI mode" + depends on ARCH_ASPEED || COMPILE_TEST + depends on OF + help + This enables support for the Firmware Memory controller (FMC) + in the Aspeed AST2600, AST2500 and AST2400 SoCs when attached + to SPI NOR chips, and support for the SPI flash memory + controller (SPI) for the host firmware. The implementation + only supports SPI NOR. + config SPI_ATMEL tristate "Atmel SPI Controller" depends on ARCH_AT91 || COMPILE_TEST @@ -414,15 +425,14 @@ config SPI_IMG_SPFI config SPI_IMX tristate "Freescale i.MX SPI controllers" depends on ARCH_MXC || COMPILE_TEST - select SPI_BITBANG help This enables support for the Freescale i.MX SPI controllers. config SPI_INGENIC - tristate "Ingenic JZ47xx SoCs SPI controller" + tristate "Ingenic SoCs SPI controller" depends on MACH_INGENIC || COMPILE_TEST help - This enables support for the Ingenic JZ47xx SoCs SPI controller. + This enables support for the Ingenic SoCs SPI controller. To compile this driver as a module, choose M here: the module will be called spi-ingenic. @@ -590,6 +600,16 @@ config SPI_MTK_NOR SPI interface as well as several SPI NOR specific instructions via SPI MEM interface. +config SPI_MTK_SNFI + tristate "MediaTek SPI NAND Flash Interface" + depends on ARCH_MEDIATEK || COMPILE_TEST + depends on MTD_NAND_ECC_MEDIATEK + help + This enables support for SPI-NAND mode on the MediaTek NAND + Flash Interface found on MediaTek ARM SoCs. This controller + is implemented as a SPI-MEM controller with pipelined ECC + capcability. + config SPI_NPCM_FIU tristate "Nuvoton NPCM FLASH Interface Unit" depends on ARCH_NPCM || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 3aa28ed3f761..0f44eb6083a5 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_SPI_ALTERA_CORE) += spi-altera-core.o obj-$(CONFIG_SPI_ALTERA_DFL) += spi-altera-dfl.o obj-$(CONFIG_SPI_AR934X) += spi-ar934x.o obj-$(CONFIG_SPI_ARMADA_3700) += spi-armada-3700.o +obj-$(CONFIG_SPI_ASPEED_SMC) += spi-aspeed-smc.o obj-$(CONFIG_SPI_ATMEL) += spi-atmel.o obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o obj-$(CONFIG_SPI_AT91_USART) += spi-at91-usart.o @@ -76,6 +77,7 @@ obj-$(CONFIG_SPI_MPC52xx) += spi-mpc52xx.o obj-$(CONFIG_SPI_MT65XX) += spi-mt65xx.o obj-$(CONFIG_SPI_MT7621) += spi-mt7621.o obj-$(CONFIG_SPI_MTK_NOR) += spi-mtk-nor.o +obj-$(CONFIG_SPI_MTK_SNFI) += spi-mtk-snfi.o obj-$(CONFIG_SPI_MXIC) += spi-mxic.o obj-$(CONFIG_SPI_MXS) += spi-mxs.o obj-$(CONFIG_SPI_NPCM_FIU) += spi-npcm-fiu.o diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c index 938017a60c8e..480c0c8c18e4 100644 --- a/drivers/spi/atmel-quadspi.c +++ b/drivers/spi/atmel-quadspi.c @@ -288,12 +288,6 @@ static bool atmel_qspi_supports_op(struct spi_mem *mem, op->dummy.nbytes == 0) return false; - /* DTR ops not supported. */ - if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr) - return false; - if (op->cmd.nbytes != 1) - return false; - return true; } diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c new file mode 100644 index 000000000000..496f3e1e9079 --- /dev/null +++ b/drivers/spi/spi-aspeed-smc.c @@ -0,0 +1,1210 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ASPEED FMC/SPI Memory Controller Driver + * + * Copyright (c) 2015-2022, IBM Corporation. + * Copyright (c) 2020, ASPEED Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DEVICE_NAME "spi-aspeed-smc" + +/* Type setting Register */ +#define CONFIG_REG 0x0 +#define CONFIG_TYPE_SPI 0x2 + +/* CE Control Register */ +#define CE_CTRL_REG 0x4 + +/* CEx Control Register */ +#define CE0_CTRL_REG 0x10 +#define CTRL_IO_MODE_MASK GENMASK(30, 28) +#define CTRL_IO_SINGLE_DATA 0x0 +#define CTRL_IO_DUAL_DATA BIT(29) +#define CTRL_IO_QUAD_DATA BIT(30) +#define CTRL_COMMAND_SHIFT 16 +#define CTRL_IO_ADDRESS_4B BIT(13) /* AST2400 SPI only */ +#define CTRL_IO_DUMMY_SET(dummy) \ + (((((dummy) >> 2) & 0x1) << 14) | (((dummy) & 0x3) << 6)) +#define CTRL_FREQ_SEL_SHIFT 8 +#define CTRL_FREQ_SEL_MASK GENMASK(11, CTRL_FREQ_SEL_SHIFT) +#define CTRL_CE_STOP_ACTIVE BIT(2) +#define CTRL_IO_MODE_CMD_MASK GENMASK(1, 0) +#define CTRL_IO_MODE_NORMAL 0x0 +#define CTRL_IO_MODE_READ 0x1 +#define CTRL_IO_MODE_WRITE 0x2 +#define CTRL_IO_MODE_USER 0x3 + +#define CTRL_IO_CMD_MASK 0xf0ff40c3 + +/* CEx Address Decoding Range Register */ +#define CE0_SEGMENT_ADDR_REG 0x30 + +/* CEx Read timing compensation register */ +#define CE0_TIMING_COMPENSATION_REG 0x94 + +enum aspeed_spi_ctl_reg_value { + ASPEED_SPI_BASE, + ASPEED_SPI_READ, + ASPEED_SPI_WRITE, + ASPEED_SPI_MAX, +}; + +struct aspeed_spi; + +struct aspeed_spi_chip { + struct aspeed_spi *aspi; + u32 cs; + void __iomem *ctl; + void __iomem *ahb_base; + u32 ahb_window_size; + u32 ctl_val[ASPEED_SPI_MAX]; + u32 clk_freq; +}; + +struct aspeed_spi_data { + u32 ctl0; + u32 max_cs; + bool hastype; + u32 mode_bits; + u32 we0; + u32 timing; + u32 hclk_mask; + u32 hdiv_max; + + u32 (*segment_start)(struct aspeed_spi *aspi, u32 reg); + u32 (*segment_end)(struct aspeed_spi *aspi, u32 reg); + u32 (*segment_reg)(struct aspeed_spi *aspi, u32 start, u32 end); + int (*calibrate)(struct aspeed_spi_chip *chip, u32 hdiv, + const u8 *golden_buf, u8 *test_buf); +}; + +#define ASPEED_SPI_MAX_NUM_CS 5 + +struct aspeed_spi { + const struct aspeed_spi_data *data; + + void __iomem *regs; + void __iomem *ahb_base; + u32 ahb_base_phy; + u32 ahb_window_size; + struct device *dev; + + struct clk *clk; + u32 clk_freq; + + struct aspeed_spi_chip chips[ASPEED_SPI_MAX_NUM_CS]; +}; + +static u32 aspeed_spi_get_io_mode(const struct spi_mem_op *op) +{ + switch (op->data.buswidth) { + case 1: + return CTRL_IO_SINGLE_DATA; + case 2: + return CTRL_IO_DUAL_DATA; + case 4: + return CTRL_IO_QUAD_DATA; + default: + return CTRL_IO_SINGLE_DATA; + } +} + +static void aspeed_spi_set_io_mode(struct aspeed_spi_chip *chip, u32 io_mode) +{ + u32 ctl; + + if (io_mode > 0) { + ctl = readl(chip->ctl) & ~CTRL_IO_MODE_MASK; + ctl |= io_mode; + writel(ctl, chip->ctl); + } +} + +static void aspeed_spi_start_user(struct aspeed_spi_chip *chip) +{ + u32 ctl = chip->ctl_val[ASPEED_SPI_BASE]; + + ctl |= CTRL_IO_MODE_USER | CTRL_CE_STOP_ACTIVE; + writel(ctl, chip->ctl); + + ctl &= ~CTRL_CE_STOP_ACTIVE; + writel(ctl, chip->ctl); +} + +static void aspeed_spi_stop_user(struct aspeed_spi_chip *chip) +{ + u32 ctl = chip->ctl_val[ASPEED_SPI_READ] | + CTRL_IO_MODE_USER | CTRL_CE_STOP_ACTIVE; + + writel(ctl, chip->ctl); + + /* Restore defaults */ + writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); +} + +static int aspeed_spi_read_from_ahb(void *buf, void __iomem *src, size_t len) +{ + size_t offset = 0; + + if (IS_ALIGNED((uintptr_t)src, sizeof(uintptr_t)) && + IS_ALIGNED((uintptr_t)buf, sizeof(uintptr_t))) { + ioread32_rep(src, buf, len >> 2); + offset = len & ~0x3; + len -= offset; + } + ioread8_rep(src, (u8 *)buf + offset, len); + return 0; +} + +static int aspeed_spi_write_to_ahb(void __iomem *dst, const void *buf, size_t len) +{ + size_t offset = 0; + + if (IS_ALIGNED((uintptr_t)dst, sizeof(uintptr_t)) && + IS_ALIGNED((uintptr_t)buf, sizeof(uintptr_t))) { + iowrite32_rep(dst, buf, len >> 2); + offset = len & ~0x3; + len -= offset; + } + iowrite8_rep(dst, (const u8 *)buf + offset, len); + return 0; +} + +static int aspeed_spi_send_cmd_addr(struct aspeed_spi_chip *chip, u8 addr_nbytes, + u64 offset, u32 opcode) +{ + __be32 temp; + u32 cmdaddr; + + switch (addr_nbytes) { + case 3: + cmdaddr = offset & 0xFFFFFF; + cmdaddr |= opcode << 24; + + temp = cpu_to_be32(cmdaddr); + aspeed_spi_write_to_ahb(chip->ahb_base, &temp, 4); + break; + case 4: + temp = cpu_to_be32(offset); + aspeed_spi_write_to_ahb(chip->ahb_base, &opcode, 1); + aspeed_spi_write_to_ahb(chip->ahb_base, &temp, 4); + break; + default: + WARN_ONCE(1, "Unexpected address width %u", addr_nbytes); + return -EOPNOTSUPP; + } + return 0; +} + +static int aspeed_spi_read_reg(struct aspeed_spi_chip *chip, + const struct spi_mem_op *op) +{ + aspeed_spi_start_user(chip); + aspeed_spi_write_to_ahb(chip->ahb_base, &op->cmd.opcode, 1); + aspeed_spi_read_from_ahb(op->data.buf.in, + chip->ahb_base, op->data.nbytes); + aspeed_spi_stop_user(chip); + return 0; +} + +static int aspeed_spi_write_reg(struct aspeed_spi_chip *chip, + const struct spi_mem_op *op) +{ + aspeed_spi_start_user(chip); + aspeed_spi_write_to_ahb(chip->ahb_base, &op->cmd.opcode, 1); + aspeed_spi_write_to_ahb(chip->ahb_base, op->data.buf.out, + op->data.nbytes); + aspeed_spi_stop_user(chip); + return 0; +} + +static ssize_t aspeed_spi_read_user(struct aspeed_spi_chip *chip, + const struct spi_mem_op *op, + u64 offset, size_t len, void *buf) +{ + int io_mode = aspeed_spi_get_io_mode(op); + u8 dummy = 0xFF; + int i; + int ret; + + aspeed_spi_start_user(chip); + + ret = aspeed_spi_send_cmd_addr(chip, op->addr.nbytes, offset, op->cmd.opcode); + if (ret < 0) + return ret; + + if (op->dummy.buswidth && op->dummy.nbytes) { + for (i = 0; i < op->dummy.nbytes / op->dummy.buswidth; i++) + aspeed_spi_write_to_ahb(chip->ahb_base, &dummy, sizeof(dummy)); + } + + aspeed_spi_set_io_mode(chip, io_mode); + + aspeed_spi_read_from_ahb(buf, chip->ahb_base, len); + aspeed_spi_stop_user(chip); + return 0; +} + +static ssize_t aspeed_spi_write_user(struct aspeed_spi_chip *chip, + const struct spi_mem_op *op) +{ + int ret; + + aspeed_spi_start_user(chip); + ret = aspeed_spi_send_cmd_addr(chip, op->addr.nbytes, op->addr.val, op->cmd.opcode); + if (ret < 0) + return ret; + aspeed_spi_write_to_ahb(chip->ahb_base, op->data.buf.out, op->data.nbytes); + aspeed_spi_stop_user(chip); + return 0; +} + +/* support for 1-1-1, 1-1-2 or 1-1-4 */ +static bool aspeed_spi_supports_op(struct spi_mem *mem, const struct spi_mem_op *op) +{ + if (op->cmd.buswidth > 1) + return false; + + if (op->addr.nbytes != 0) { + if (op->addr.buswidth > 1) + return false; + if (op->addr.nbytes < 3 || op->addr.nbytes > 4) + return false; + } + + if (op->dummy.nbytes != 0) { + if (op->dummy.buswidth > 1 || op->dummy.nbytes > 7) + return false; + } + + if (op->data.nbytes != 0 && op->data.buswidth > 4) + return false; + + return spi_mem_default_supports_op(mem, op); +} + +static const struct aspeed_spi_data ast2400_spi_data; + +static int do_aspeed_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) +{ + struct aspeed_spi *aspi = spi_controller_get_devdata(mem->spi->master); + struct aspeed_spi_chip *chip = &aspi->chips[mem->spi->chip_select]; + u32 addr_mode, addr_mode_backup; + u32 ctl_val; + int ret = 0; + + dev_dbg(aspi->dev, + "CE%d %s OP %#x mode:%d.%d.%d.%d naddr:%#x ndummies:%#x len:%#x", + chip->cs, op->data.dir == SPI_MEM_DATA_IN ? "read" : "write", + op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth, + op->dummy.buswidth, op->data.buswidth, + op->addr.nbytes, op->dummy.nbytes, op->data.nbytes); + + addr_mode = readl(aspi->regs + CE_CTRL_REG); + addr_mode_backup = addr_mode; + + ctl_val = chip->ctl_val[ASPEED_SPI_BASE]; + ctl_val &= ~CTRL_IO_CMD_MASK; + + ctl_val |= op->cmd.opcode << CTRL_COMMAND_SHIFT; + + /* 4BYTE address mode */ + if (op->addr.nbytes) { + if (op->addr.nbytes == 4) + addr_mode |= (0x11 << chip->cs); + else + addr_mode &= ~(0x11 << chip->cs); + + if (op->addr.nbytes == 4 && chip->aspi->data == &ast2400_spi_data) + ctl_val |= CTRL_IO_ADDRESS_4B; + } + + if (op->dummy.nbytes) + ctl_val |= CTRL_IO_DUMMY_SET(op->dummy.nbytes / op->dummy.buswidth); + + if (op->data.nbytes) + ctl_val |= aspeed_spi_get_io_mode(op); + + if (op->data.dir == SPI_MEM_DATA_OUT) + ctl_val |= CTRL_IO_MODE_WRITE; + else + ctl_val |= CTRL_IO_MODE_READ; + + if (addr_mode != addr_mode_backup) + writel(addr_mode, aspi->regs + CE_CTRL_REG); + writel(ctl_val, chip->ctl); + + if (op->data.dir == SPI_MEM_DATA_IN) { + if (!op->addr.nbytes) + ret = aspeed_spi_read_reg(chip, op); + else + ret = aspeed_spi_read_user(chip, op, op->addr.val, + op->data.nbytes, op->data.buf.in); + } else { + if (!op->addr.nbytes) + ret = aspeed_spi_write_reg(chip, op); + else + ret = aspeed_spi_write_user(chip, op); + } + + /* Restore defaults */ + if (addr_mode != addr_mode_backup) + writel(addr_mode_backup, aspi->regs + CE_CTRL_REG); + writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); + return ret; +} + +static int aspeed_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) +{ + int ret; + + ret = do_aspeed_spi_exec_op(mem, op); + if (ret) + dev_err(&mem->spi->dev, "operation failed: %d\n", ret); + return ret; +} + +static const char *aspeed_spi_get_name(struct spi_mem *mem) +{ + struct aspeed_spi *aspi = spi_controller_get_devdata(mem->spi->master); + struct device *dev = aspi->dev; + + return devm_kasprintf(dev, GFP_KERNEL, "%s.%d", dev_name(dev), mem->spi->chip_select); +} + +struct aspeed_spi_window { + u32 cs; + u32 offset; + u32 size; +}; + +static void aspeed_spi_get_windows(struct aspeed_spi *aspi, + struct aspeed_spi_window windows[ASPEED_SPI_MAX_NUM_CS]) +{ + const struct aspeed_spi_data *data = aspi->data; + u32 reg_val; + u32 cs; + + for (cs = 0; cs < aspi->data->max_cs; cs++) { + reg_val = readl(aspi->regs + CE0_SEGMENT_ADDR_REG + cs * 4); + windows[cs].cs = cs; + windows[cs].size = data->segment_end(aspi, reg_val) - + data->segment_start(aspi, reg_val); + windows[cs].offset = cs ? windows[cs - 1].offset + windows[cs - 1].size : 0; + dev_vdbg(aspi->dev, "CE%d offset=0x%.8x size=0x%x\n", cs, + windows[cs].offset, windows[cs].size); + } +} + +/* + * On the AST2600, some CE windows are closed by default at reset but + * U-Boot should open all. + */ +static int aspeed_spi_chip_set_default_window(struct aspeed_spi_chip *chip) +{ + struct aspeed_spi *aspi = chip->aspi; + struct aspeed_spi_window windows[ASPEED_SPI_MAX_NUM_CS] = { 0 }; + struct aspeed_spi_window *win = &windows[chip->cs]; + + /* No segment registers for the AST2400 SPI controller */ + if (aspi->data == &ast2400_spi_data) { + win->offset = 0; + win->size = aspi->ahb_window_size; + } else { + aspeed_spi_get_windows(aspi, windows); + } + + chip->ahb_base = aspi->ahb_base + win->offset; + chip->ahb_window_size = win->size; + + dev_dbg(aspi->dev, "CE%d default window [ 0x%.8x - 0x%.8x ] %dMB", + chip->cs, aspi->ahb_base_phy + win->offset, + aspi->ahb_base_phy + win->offset + win->size - 1, + win->size >> 20); + + return chip->ahb_window_size ? 0 : -1; +} + +static int aspeed_spi_set_window(struct aspeed_spi *aspi, + const struct aspeed_spi_window *win) +{ + u32 start = aspi->ahb_base_phy + win->offset; + u32 end = start + win->size; + void __iomem *seg_reg = aspi->regs + CE0_SEGMENT_ADDR_REG + win->cs * 4; + u32 seg_val_backup = readl(seg_reg); + u32 seg_val = aspi->data->segment_reg(aspi, start, end); + + if (seg_val == seg_val_backup) + return 0; + + writel(seg_val, seg_reg); + + /* + * Restore initial value if something goes wrong else we could + * loose access to the chip. + */ + if (seg_val != readl(seg_reg)) { + dev_err(aspi->dev, "CE%d invalid window [ 0x%.8x - 0x%.8x ] %dMB", + win->cs, start, end - 1, win->size >> 20); + writel(seg_val_backup, seg_reg); + return -EIO; + } + + if (win->size) + dev_dbg(aspi->dev, "CE%d new window [ 0x%.8x - 0x%.8x ] %dMB", + win->cs, start, end - 1, win->size >> 20); + else + dev_dbg(aspi->dev, "CE%d window closed", win->cs); + + return 0; +} + +/* + * Yet to be done when possible : + * - Align mappings on flash size (we don't have the info) + * - ioremap each window, not strictly necessary since the overall window + * is correct. + */ +static const struct aspeed_spi_data ast2500_spi_data; +static const struct aspeed_spi_data ast2600_spi_data; +static const struct aspeed_spi_data ast2600_fmc_data; + +static int aspeed_spi_chip_adjust_window(struct aspeed_spi_chip *chip, + u32 local_offset, u32 size) +{ + struct aspeed_spi *aspi = chip->aspi; + struct aspeed_spi_window windows[ASPEED_SPI_MAX_NUM_CS] = { 0 }; + struct aspeed_spi_window *win = &windows[chip->cs]; + int ret; + + /* No segment registers for the AST2400 SPI controller */ + if (aspi->data == &ast2400_spi_data) + return 0; + + /* + * Due to an HW issue on the AST2500 SPI controller, the CE0 + * window size should be smaller than the maximum 128MB. + */ + if (aspi->data == &ast2500_spi_data && chip->cs == 0 && size == SZ_128M) { + size = 120 << 20; + dev_info(aspi->dev, "CE%d window resized to %dMB (AST2500 HW quirk)", + chip->cs, size >> 20); + } + + /* + * The decoding size of AST2600 SPI controller should set at + * least 2MB. + */ + if ((aspi->data == &ast2600_spi_data || aspi->data == &ast2600_fmc_data) && + size < SZ_2M) { + size = SZ_2M; + dev_info(aspi->dev, "CE%d window resized to %dMB (AST2600 Decoding)", + chip->cs, size >> 20); + } + + aspeed_spi_get_windows(aspi, windows); + + /* Adjust this chip window */ + win->offset += local_offset; + win->size = size; + + if (win->offset + win->size > aspi->ahb_window_size) { + win->size = aspi->ahb_window_size - win->offset; + dev_warn(aspi->dev, "CE%d window resized to %dMB", chip->cs, win->size >> 20); + } + + ret = aspeed_spi_set_window(aspi, win); + if (ret) + return ret; + + /* Update chip mapping info */ + chip->ahb_base = aspi->ahb_base + win->offset; + chip->ahb_window_size = win->size; + + /* + * Also adjust next chip window to make sure that it does not + * overlap with the current window. + */ + if (chip->cs < aspi->data->max_cs - 1) { + struct aspeed_spi_window *next = &windows[chip->cs + 1]; + + /* Change offset and size to keep the same end address */ + if ((next->offset + next->size) > (win->offset + win->size)) + next->size = (next->offset + next->size) - (win->offset + win->size); + else + next->size = 0; + next->offset = win->offset + win->size; + + aspeed_spi_set_window(aspi, next); + } + return 0; +} + +static int aspeed_spi_do_calibration(struct aspeed_spi_chip *chip); + +static int aspeed_spi_dirmap_create(struct spi_mem_dirmap_desc *desc) +{ + struct aspeed_spi *aspi = spi_controller_get_devdata(desc->mem->spi->master); + struct aspeed_spi_chip *chip = &aspi->chips[desc->mem->spi->chip_select]; + struct spi_mem_op *op = &desc->info.op_tmpl; + u32 ctl_val; + int ret = 0; + + chip->clk_freq = desc->mem->spi->max_speed_hz; + + /* Only for reads */ + if (op->data.dir != SPI_MEM_DATA_IN) + return -EOPNOTSUPP; + + aspeed_spi_chip_adjust_window(chip, desc->info.offset, desc->info.length); + + if (desc->info.length > chip->ahb_window_size) + dev_warn(aspi->dev, "CE%d window (%dMB) too small for mapping", + chip->cs, chip->ahb_window_size >> 20); + + /* Define the default IO read settings */ + ctl_val = readl(chip->ctl) & ~CTRL_IO_CMD_MASK; + ctl_val |= aspeed_spi_get_io_mode(op) | + op->cmd.opcode << CTRL_COMMAND_SHIFT | + CTRL_IO_DUMMY_SET(op->dummy.nbytes / op->dummy.buswidth) | + CTRL_IO_MODE_READ; + + /* Tune 4BYTE address mode */ + if (op->addr.nbytes) { + u32 addr_mode = readl(aspi->regs + CE_CTRL_REG); + + if (op->addr.nbytes == 4) + addr_mode |= (0x11 << chip->cs); + else + addr_mode &= ~(0x11 << chip->cs); + writel(addr_mode, aspi->regs + CE_CTRL_REG); + + /* AST2400 SPI controller sets 4BYTE address mode in + * CE0 Control Register + */ + if (op->addr.nbytes == 4 && chip->aspi->data == &ast2400_spi_data) + ctl_val |= CTRL_IO_ADDRESS_4B; + } + + /* READ mode is the controller default setting */ + chip->ctl_val[ASPEED_SPI_READ] = ctl_val; + writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); + + ret = aspeed_spi_do_calibration(chip); + + dev_info(aspi->dev, "CE%d read buswidth:%d [0x%08x]\n", + chip->cs, op->data.buswidth, chip->ctl_val[ASPEED_SPI_READ]); + + return ret; +} + +static ssize_t aspeed_spi_dirmap_read(struct spi_mem_dirmap_desc *desc, + u64 offset, size_t len, void *buf) +{ + struct aspeed_spi *aspi = spi_controller_get_devdata(desc->mem->spi->master); + struct aspeed_spi_chip *chip = &aspi->chips[desc->mem->spi->chip_select]; + + /* Switch to USER command mode if mapping window is too small */ + if (chip->ahb_window_size < offset + len) { + int ret; + + ret = aspeed_spi_read_user(chip, &desc->info.op_tmpl, offset, len, buf); + if (ret < 0) + return ret; + } else { + memcpy_fromio(buf, chip->ahb_base + offset, len); + } + + return len; +} + +static const struct spi_controller_mem_ops aspeed_spi_mem_ops = { + .supports_op = aspeed_spi_supports_op, + .exec_op = aspeed_spi_exec_op, + .get_name = aspeed_spi_get_name, + .dirmap_create = aspeed_spi_dirmap_create, + .dirmap_read = aspeed_spi_dirmap_read, +}; + +static void aspeed_spi_chip_set_type(struct aspeed_spi *aspi, unsigned int cs, int type) +{ + u32 reg; + + reg = readl(aspi->regs + CONFIG_REG); + reg &= ~(0x3 << (cs * 2)); + reg |= type << (cs * 2); + writel(reg, aspi->regs + CONFIG_REG); +} + +static void aspeed_spi_chip_enable(struct aspeed_spi *aspi, unsigned int cs, bool enable) +{ + u32 we_bit = BIT(aspi->data->we0 + cs); + u32 reg = readl(aspi->regs + CONFIG_REG); + + if (enable) + reg |= we_bit; + else + reg &= ~we_bit; + writel(reg, aspi->regs + CONFIG_REG); +} + +static int aspeed_spi_setup(struct spi_device *spi) +{ + struct aspeed_spi *aspi = spi_controller_get_devdata(spi->master); + const struct aspeed_spi_data *data = aspi->data; + unsigned int cs = spi->chip_select; + struct aspeed_spi_chip *chip = &aspi->chips[cs]; + + chip->aspi = aspi; + chip->cs = cs; + chip->ctl = aspi->regs + data->ctl0 + cs * 4; + + /* The driver only supports SPI type flash */ + if (data->hastype) + aspeed_spi_chip_set_type(aspi, cs, CONFIG_TYPE_SPI); + + if (aspeed_spi_chip_set_default_window(chip) < 0) { + dev_warn(aspi->dev, "CE%d window invalid", cs); + return -EINVAL; + } + + aspeed_spi_chip_enable(aspi, cs, true); + + chip->ctl_val[ASPEED_SPI_BASE] = CTRL_CE_STOP_ACTIVE | CTRL_IO_MODE_USER; + + dev_dbg(aspi->dev, "CE%d setup done\n", cs); + return 0; +} + +static void aspeed_spi_cleanup(struct spi_device *spi) +{ + struct aspeed_spi *aspi = spi_controller_get_devdata(spi->master); + unsigned int cs = spi->chip_select; + + aspeed_spi_chip_enable(aspi, cs, false); + + dev_dbg(aspi->dev, "CE%d cleanup done\n", cs); +} + +static void aspeed_spi_enable(struct aspeed_spi *aspi, bool enable) +{ + int cs; + + for (cs = 0; cs < aspi->data->max_cs; cs++) + aspeed_spi_chip_enable(aspi, cs, enable); +} + +static int aspeed_spi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct aspeed_spi_data *data; + struct spi_controller *ctlr; + struct aspeed_spi *aspi; + struct resource *res; + int ret; + + data = of_device_get_match_data(&pdev->dev); + if (!data) + return -ENODEV; + + ctlr = devm_spi_alloc_master(dev, sizeof(*aspi)); + if (!ctlr) + return -ENOMEM; + + aspi = spi_controller_get_devdata(ctlr); + platform_set_drvdata(pdev, aspi); + aspi->data = data; + aspi->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + aspi->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(aspi->regs)) { + dev_err(dev, "missing AHB register window\n"); + return PTR_ERR(aspi->regs); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + aspi->ahb_base = devm_ioremap_resource(dev, res); + if (IS_ERR(aspi->ahb_base)) { + dev_err(dev, "missing AHB mapping window\n"); + return PTR_ERR(aspi->ahb_base); + } + + aspi->ahb_window_size = resource_size(res); + aspi->ahb_base_phy = res->start; + + aspi->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(aspi->clk)) { + dev_err(dev, "missing clock\n"); + return PTR_ERR(aspi->clk); + } + + aspi->clk_freq = clk_get_rate(aspi->clk); + if (!aspi->clk_freq) { + dev_err(dev, "invalid clock\n"); + return -EINVAL; + } + + ret = clk_prepare_enable(aspi->clk); + if (ret) { + dev_err(dev, "can not enable the clock\n"); + return ret; + } + + /* IRQ is for DMA, which the driver doesn't support yet */ + + ctlr->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL | data->mode_bits; + ctlr->bus_num = pdev->id; + ctlr->mem_ops = &aspeed_spi_mem_ops; + ctlr->setup = aspeed_spi_setup; + ctlr->cleanup = aspeed_spi_cleanup; + ctlr->num_chipselect = data->max_cs; + ctlr->dev.of_node = dev->of_node; + + ret = devm_spi_register_controller(dev, ctlr); + if (ret) { + dev_err(&pdev->dev, "spi_register_controller failed\n"); + goto disable_clk; + } + return 0; + +disable_clk: + clk_disable_unprepare(aspi->clk); + return ret; +} + +static int aspeed_spi_remove(struct platform_device *pdev) +{ + struct aspeed_spi *aspi = platform_get_drvdata(pdev); + + aspeed_spi_enable(aspi, false); + clk_disable_unprepare(aspi->clk); + return 0; +} + +/* + * AHB mappings + */ + +/* + * The Segment Registers of the AST2400 and AST2500 use a 8MB unit. + * The address range is encoded with absolute addresses in the overall + * mapping window. + */ +static u32 aspeed_spi_segment_start(struct aspeed_spi *aspi, u32 reg) +{ + return ((reg >> 16) & 0xFF) << 23; +} + +static u32 aspeed_spi_segment_end(struct aspeed_spi *aspi, u32 reg) +{ + return ((reg >> 24) & 0xFF) << 23; +} + +static u32 aspeed_spi_segment_reg(struct aspeed_spi *aspi, u32 start, u32 end) +{ + return (((start >> 23) & 0xFF) << 16) | (((end >> 23) & 0xFF) << 24); +} + +/* + * The Segment Registers of the AST2600 use a 1MB unit. The address + * range is encoded with offsets in the overall mapping window. + */ + +#define AST2600_SEG_ADDR_MASK 0x0ff00000 + +static u32 aspeed_spi_segment_ast2600_start(struct aspeed_spi *aspi, + u32 reg) +{ + u32 start_offset = (reg << 16) & AST2600_SEG_ADDR_MASK; + + return aspi->ahb_base_phy + start_offset; +} + +static u32 aspeed_spi_segment_ast2600_end(struct aspeed_spi *aspi, + u32 reg) +{ + u32 end_offset = reg & AST2600_SEG_ADDR_MASK; + + /* segment is disabled */ + if (!end_offset) + return aspi->ahb_base_phy; + + return aspi->ahb_base_phy + end_offset + 0x100000; +} + +static u32 aspeed_spi_segment_ast2600_reg(struct aspeed_spi *aspi, + u32 start, u32 end) +{ + /* disable zero size segments */ + if (start == end) + return 0; + + return ((start & AST2600_SEG_ADDR_MASK) >> 16) | + ((end - 1) & AST2600_SEG_ADDR_MASK); +} + +/* + * Read timing compensation sequences + */ + +#define CALIBRATE_BUF_SIZE SZ_16K + +static bool aspeed_spi_check_reads(struct aspeed_spi_chip *chip, + const u8 *golden_buf, u8 *test_buf) +{ + int i; + + for (i = 0; i < 10; i++) { + memcpy_fromio(test_buf, chip->ahb_base, CALIBRATE_BUF_SIZE); + if (memcmp(test_buf, golden_buf, CALIBRATE_BUF_SIZE) != 0) { +#if defined(VERBOSE_DEBUG) + print_hex_dump_bytes(DEVICE_NAME " fail: ", DUMP_PREFIX_NONE, + test_buf, 0x100); +#endif + return false; + } + } + return true; +} + +#define FREAD_TPASS(i) (((i) / 2) | (((i) & 1) ? 0 : 8)) + +/* + * The timing register is shared by all devices. Only update for CE0. + */ +static int aspeed_spi_calibrate(struct aspeed_spi_chip *chip, u32 hdiv, + const u8 *golden_buf, u8 *test_buf) +{ + struct aspeed_spi *aspi = chip->aspi; + const struct aspeed_spi_data *data = aspi->data; + int i; + int good_pass = -1, pass_count = 0; + u32 shift = (hdiv - 1) << 2; + u32 mask = ~(0xfu << shift); + u32 fread_timing_val = 0; + + /* Try HCLK delay 0..5, each one with/without delay and look for a + * good pair. + */ + for (i = 0; i < 12; i++) { + bool pass; + + if (chip->cs == 0) { + fread_timing_val &= mask; + fread_timing_val |= FREAD_TPASS(i) << shift; + writel(fread_timing_val, aspi->regs + data->timing); + } + pass = aspeed_spi_check_reads(chip, golden_buf, test_buf); + dev_dbg(aspi->dev, + " * [%08x] %d HCLK delay, %dns DI delay : %s", + fread_timing_val, i / 2, (i & 1) ? 0 : 4, + pass ? "PASS" : "FAIL"); + if (pass) { + pass_count++; + if (pass_count == 3) { + good_pass = i - 1; + break; + } + } else { + pass_count = 0; + } + } + + /* No good setting for this frequency */ + if (good_pass < 0) + return -1; + + /* We have at least one pass of margin, let's use first pass */ + if (chip->cs == 0) { + fread_timing_val &= mask; + fread_timing_val |= FREAD_TPASS(good_pass) << shift; + writel(fread_timing_val, aspi->regs + data->timing); + } + dev_dbg(aspi->dev, " * -> good is pass %d [0x%08x]", + good_pass, fread_timing_val); + return 0; +} + +static bool aspeed_spi_check_calib_data(const u8 *test_buf, u32 size) +{ + const u32 *tb32 = (const u32 *)test_buf; + u32 i, cnt = 0; + + /* We check if we have enough words that are neither all 0 + * nor all 1's so the calibration can be considered valid. + * + * I use an arbitrary threshold for now of 64 + */ + size >>= 2; + for (i = 0; i < size; i++) { + if (tb32[i] != 0 && tb32[i] != 0xffffffff) + cnt++; + } + return cnt >= 64; +} + +static const u32 aspeed_spi_hclk_divs[] = { + 0xf, /* HCLK */ + 0x7, /* HCLK/2 */ + 0xe, /* HCLK/3 */ + 0x6, /* HCLK/4 */ + 0xd, /* HCLK/5 */ +}; + +#define ASPEED_SPI_HCLK_DIV(i) \ + (aspeed_spi_hclk_divs[(i) - 1] << CTRL_FREQ_SEL_SHIFT) + +static int aspeed_spi_do_calibration(struct aspeed_spi_chip *chip) +{ + struct aspeed_spi *aspi = chip->aspi; + const struct aspeed_spi_data *data = aspi->data; + u32 ahb_freq = aspi->clk_freq; + u32 max_freq = chip->clk_freq; + u32 ctl_val; + u8 *golden_buf = NULL; + u8 *test_buf = NULL; + int i, rc, best_div = -1; + + dev_dbg(aspi->dev, "calculate timing compensation - AHB freq: %d MHz", + ahb_freq / 1000000); + + /* + * use the related low frequency to get check calibration data + * and get golden data. + */ + ctl_val = chip->ctl_val[ASPEED_SPI_READ] & data->hclk_mask; + writel(ctl_val, chip->ctl); + + test_buf = kzalloc(CALIBRATE_BUF_SIZE * 2, GFP_KERNEL); + if (!test_buf) + return -ENOMEM; + + golden_buf = test_buf + CALIBRATE_BUF_SIZE; + + memcpy_fromio(golden_buf, chip->ahb_base, CALIBRATE_BUF_SIZE); + if (!aspeed_spi_check_calib_data(golden_buf, CALIBRATE_BUF_SIZE)) { + dev_info(aspi->dev, "Calibration area too uniform, using low speed"); + goto no_calib; + } + +#if defined(VERBOSE_DEBUG) + print_hex_dump_bytes(DEVICE_NAME " good: ", DUMP_PREFIX_NONE, + golden_buf, 0x100); +#endif + + /* Now we iterate the HCLK dividers until we find our breaking point */ + for (i = ARRAY_SIZE(aspeed_spi_hclk_divs); i > data->hdiv_max - 1; i--) { + u32 tv, freq; + + freq = ahb_freq / i; + if (freq > max_freq) + continue; + + /* Set the timing */ + tv = chip->ctl_val[ASPEED_SPI_READ] | ASPEED_SPI_HCLK_DIV(i); + writel(tv, chip->ctl); + dev_dbg(aspi->dev, "Trying HCLK/%d [%08x] ...", i, tv); + rc = data->calibrate(chip, i, golden_buf, test_buf); + if (rc == 0) + best_div = i; + } + + /* Nothing found ? */ + if (best_div < 0) { + dev_warn(aspi->dev, "No good frequency, using dumb slow"); + } else { + dev_dbg(aspi->dev, "Found good read timings at HCLK/%d", best_div); + + /* Record the freq */ + for (i = 0; i < ASPEED_SPI_MAX; i++) + chip->ctl_val[i] = (chip->ctl_val[i] & data->hclk_mask) | + ASPEED_SPI_HCLK_DIV(best_div); + } + +no_calib: + writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); + kfree(test_buf); + return 0; +} + +#define TIMING_DELAY_DI BIT(3) +#define TIMING_DELAY_HCYCLE_MAX 5 +#define TIMING_REG_AST2600(chip) \ + ((chip)->aspi->regs + (chip)->aspi->data->timing + \ + (chip)->cs * 4) + +static int aspeed_spi_ast2600_calibrate(struct aspeed_spi_chip *chip, u32 hdiv, + const u8 *golden_buf, u8 *test_buf) +{ + struct aspeed_spi *aspi = chip->aspi; + int hcycle; + u32 shift = (hdiv - 2) << 3; + u32 mask = ~(0xfu << shift); + u32 fread_timing_val = 0; + + for (hcycle = 0; hcycle <= TIMING_DELAY_HCYCLE_MAX; hcycle++) { + int delay_ns; + bool pass = false; + + fread_timing_val &= mask; + fread_timing_val |= hcycle << shift; + + /* no DI input delay first */ + writel(fread_timing_val, TIMING_REG_AST2600(chip)); + pass = aspeed_spi_check_reads(chip, golden_buf, test_buf); + dev_dbg(aspi->dev, + " * [%08x] %d HCLK delay, DI delay none : %s", + fread_timing_val, hcycle, pass ? "PASS" : "FAIL"); + if (pass) + return 0; + + /* Add DI input delays */ + fread_timing_val &= mask; + fread_timing_val |= (TIMING_DELAY_DI | hcycle) << shift; + + for (delay_ns = 0; delay_ns < 0x10; delay_ns++) { + fread_timing_val &= ~(0xf << (4 + shift)); + fread_timing_val |= delay_ns << (4 + shift); + + writel(fread_timing_val, TIMING_REG_AST2600(chip)); + pass = aspeed_spi_check_reads(chip, golden_buf, test_buf); + dev_dbg(aspi->dev, + " * [%08x] %d HCLK delay, DI delay %d.%dns : %s", + fread_timing_val, hcycle, (delay_ns + 1) / 2, + (delay_ns + 1) & 1 ? 5 : 5, pass ? "PASS" : "FAIL"); + /* + * TODO: This is optimistic. We should look + * for a working interval and save the middle + * value in the read timing register. + */ + if (pass) + return 0; + } + } + + /* No good setting for this frequency */ + return -1; +} + +/* + * Platform definitions + */ +static const struct aspeed_spi_data ast2400_fmc_data = { + .max_cs = 5, + .hastype = true, + .we0 = 16, + .ctl0 = CE0_CTRL_REG, + .timing = CE0_TIMING_COMPENSATION_REG, + .hclk_mask = 0xfffff0ff, + .hdiv_max = 1, + .calibrate = aspeed_spi_calibrate, + .segment_start = aspeed_spi_segment_start, + .segment_end = aspeed_spi_segment_end, + .segment_reg = aspeed_spi_segment_reg, +}; + +static const struct aspeed_spi_data ast2400_spi_data = { + .max_cs = 1, + .hastype = false, + .we0 = 0, + .ctl0 = 0x04, + .timing = 0x14, + .hclk_mask = 0xfffff0ff, + .hdiv_max = 1, + .calibrate = aspeed_spi_calibrate, + /* No segment registers */ +}; + +static const struct aspeed_spi_data ast2500_fmc_data = { + .max_cs = 3, + .hastype = true, + .we0 = 16, + .ctl0 = CE0_CTRL_REG, + .timing = CE0_TIMING_COMPENSATION_REG, + .hclk_mask = 0xffffd0ff, + .hdiv_max = 1, + .calibrate = aspeed_spi_calibrate, + .segment_start = aspeed_spi_segment_start, + .segment_end = aspeed_spi_segment_end, + .segment_reg = aspeed_spi_segment_reg, +}; + +static const struct aspeed_spi_data ast2500_spi_data = { + .max_cs = 2, + .hastype = false, + .we0 = 16, + .ctl0 = CE0_CTRL_REG, + .timing = CE0_TIMING_COMPENSATION_REG, + .hclk_mask = 0xffffd0ff, + .hdiv_max = 1, + .calibrate = aspeed_spi_calibrate, + .segment_start = aspeed_spi_segment_start, + .segment_end = aspeed_spi_segment_end, + .segment_reg = aspeed_spi_segment_reg, +}; + +static const struct aspeed_spi_data ast2600_fmc_data = { + .max_cs = 3, + .hastype = false, + .mode_bits = SPI_RX_QUAD | SPI_RX_QUAD, + .we0 = 16, + .ctl0 = CE0_CTRL_REG, + .timing = CE0_TIMING_COMPENSATION_REG, + .hclk_mask = 0xf0fff0ff, + .hdiv_max = 2, + .calibrate = aspeed_spi_ast2600_calibrate, + .segment_start = aspeed_spi_segment_ast2600_start, + .segment_end = aspeed_spi_segment_ast2600_end, + .segment_reg = aspeed_spi_segment_ast2600_reg, +}; + +static const struct aspeed_spi_data ast2600_spi_data = { + .max_cs = 2, + .hastype = false, + .mode_bits = SPI_RX_QUAD | SPI_RX_QUAD, + .we0 = 16, + .ctl0 = CE0_CTRL_REG, + .timing = CE0_TIMING_COMPENSATION_REG, + .hclk_mask = 0xf0fff0ff, + .hdiv_max = 2, + .calibrate = aspeed_spi_ast2600_calibrate, + .segment_start = aspeed_spi_segment_ast2600_start, + .segment_end = aspeed_spi_segment_ast2600_end, + .segment_reg = aspeed_spi_segment_ast2600_reg, +}; + +static const struct of_device_id aspeed_spi_matches[] = { + { .compatible = "aspeed,ast2400-fmc", .data = &ast2400_fmc_data }, + { .compatible = "aspeed,ast2400-spi", .data = &ast2400_spi_data }, + { .compatible = "aspeed,ast2500-fmc", .data = &ast2500_fmc_data }, + { .compatible = "aspeed,ast2500-spi", .data = &ast2500_spi_data }, + { .compatible = "aspeed,ast2600-fmc", .data = &ast2600_fmc_data }, + { .compatible = "aspeed,ast2600-spi", .data = &ast2600_spi_data }, + { } +}; +MODULE_DEVICE_TABLE(of, aspeed_spi_matches); + +static struct platform_driver aspeed_spi_driver = { + .probe = aspeed_spi_probe, + .remove = aspeed_spi_remove, + .driver = { + .name = DEVICE_NAME, + .of_match_table = aspeed_spi_matches, + } +}; + +module_platform_driver(aspeed_spi_driver); + +MODULE_DESCRIPTION("ASPEED Static Memory Controller Driver"); +MODULE_AUTHOR("Chin-Ting Kuo "); +MODULE_AUTHOR("Cedric Le Goater "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/spi-au1550.c b/drivers/spi/spi-au1550.c index 4b59a1b1bf7e..e008761298da 100644 --- a/drivers/spi/spi-au1550.c +++ b/drivers/spi/spi-au1550.c @@ -405,7 +405,7 @@ static int au1550_spi_dma_txrxb(struct spi_device *spi, struct spi_transfer *t) dma_unmap_single(hw->dev, dma_tx_addr, t->len, DMA_TO_DEVICE); - return hw->rx_count < hw->tx_count ? hw->rx_count : hw->tx_count; + return min(hw->rx_count, hw->tx_count); } static irqreturn_t au1550_spi_dma_irq_callback(struct au1550_spi *hw) @@ -539,7 +539,7 @@ static int au1550_spi_pio_txrxb(struct spi_device *spi, struct spi_transfer *t) wait_for_completion(&hw->master_done); - return hw->rx_count < hw->tx_count ? hw->rx_count : hw->tx_count; + return min(hw->rx_count, hw->tx_count); } static irqreturn_t au1550_spi_pio_irq_callback(struct au1550_spi *hw) diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index 19686fb47bb3..2b9fc8449a62 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -43,6 +43,8 @@ /* Capabilities */ #define CQSPI_SUPPORTS_OCTAL BIT(0) +#define CQSPI_OP_WIDTH(part) ((part).nbytes ? ilog2((part).buswidth) : 0) + struct cqspi_st; struct cqspi_flash_pdata { @@ -53,16 +55,12 @@ struct cqspi_flash_pdata { u32 tsd2d_ns; u32 tchsh_ns; u32 tslch_ns; - u8 inst_width; - u8 addr_width; - u8 data_width; - bool dtr; u8 cs; }; struct cqspi_st { struct platform_device *pdev; - + struct spi_master *master; struct clk *clk; unsigned int sclk; @@ -343,18 +341,18 @@ static irqreturn_t cqspi_irq_handler(int this_irq, void *dev) return IRQ_HANDLED; } -static unsigned int cqspi_calc_rdreg(struct cqspi_flash_pdata *f_pdata) +static unsigned int cqspi_calc_rdreg(const struct spi_mem_op *op) { u32 rdreg = 0; - rdreg |= f_pdata->inst_width << CQSPI_REG_RD_INSTR_TYPE_INSTR_LSB; - rdreg |= f_pdata->addr_width << CQSPI_REG_RD_INSTR_TYPE_ADDR_LSB; - rdreg |= f_pdata->data_width << CQSPI_REG_RD_INSTR_TYPE_DATA_LSB; + rdreg |= CQSPI_OP_WIDTH(op->cmd) << CQSPI_REG_RD_INSTR_TYPE_INSTR_LSB; + rdreg |= CQSPI_OP_WIDTH(op->addr) << CQSPI_REG_RD_INSTR_TYPE_ADDR_LSB; + rdreg |= CQSPI_OP_WIDTH(op->data) << CQSPI_REG_RD_INSTR_TYPE_DATA_LSB; return rdreg; } -static unsigned int cqspi_calc_dummy(const struct spi_mem_op *op, bool dtr) +static unsigned int cqspi_calc_dummy(const struct spi_mem_op *op) { unsigned int dummy_clk; @@ -362,66 +360,12 @@ static unsigned int cqspi_calc_dummy(const struct spi_mem_op *op, bool dtr) return 0; dummy_clk = op->dummy.nbytes * (8 / op->dummy.buswidth); - if (dtr) + if (op->cmd.dtr) dummy_clk /= 2; return dummy_clk; } -static int cqspi_set_protocol(struct cqspi_flash_pdata *f_pdata, - const struct spi_mem_op *op) -{ - /* - * For an op to be DTR, cmd phase along with every other non-empty - * phase should have dtr field set to 1. If an op phase has zero - * nbytes, ignore its dtr field; otherwise, check its dtr field. - */ - f_pdata->dtr = op->cmd.dtr && - (!op->addr.nbytes || op->addr.dtr) && - (!op->data.nbytes || op->data.dtr); - - f_pdata->inst_width = 0; - if (op->cmd.buswidth) - f_pdata->inst_width = ilog2(op->cmd.buswidth); - - f_pdata->addr_width = 0; - if (op->addr.buswidth) - f_pdata->addr_width = ilog2(op->addr.buswidth); - - f_pdata->data_width = 0; - if (op->data.buswidth) - f_pdata->data_width = ilog2(op->data.buswidth); - - /* Right now we only support 8-8-8 DTR mode. */ - if (f_pdata->dtr) { - switch (op->cmd.buswidth) { - case 0: - case 8: - break; - default: - return -EINVAL; - } - - switch (op->addr.buswidth) { - case 0: - case 8: - break; - default: - return -EINVAL; - } - - switch (op->data.buswidth) { - case 0: - case 8: - break; - default: - return -EINVAL; - } - } - - return 0; -} - static int cqspi_wait_idle(struct cqspi_st *cqspi) { const unsigned int poll_idle_retry = 3; @@ -503,8 +447,7 @@ static int cqspi_setup_opcode_ext(struct cqspi_flash_pdata *f_pdata, } static int cqspi_enable_dtr(struct cqspi_flash_pdata *f_pdata, - const struct spi_mem_op *op, unsigned int shift, - bool enable) + const struct spi_mem_op *op, unsigned int shift) { struct cqspi_st *cqspi = f_pdata->cqspi; void __iomem *reg_base = cqspi->iobase; @@ -517,7 +460,7 @@ static int cqspi_enable_dtr(struct cqspi_flash_pdata *f_pdata, * We enable dual byte opcode here. The callers have to set up the * extension opcode based on which type of operation it is. */ - if (enable) { + if (op->cmd.dtr) { reg |= CQSPI_REG_CONFIG_DTR_PROTO; reg |= CQSPI_REG_CONFIG_DUAL_OPCODE; @@ -549,12 +492,7 @@ static int cqspi_command_read(struct cqspi_flash_pdata *f_pdata, size_t read_len; int status; - status = cqspi_set_protocol(f_pdata, op); - if (status) - return status; - - status = cqspi_enable_dtr(f_pdata, op, CQSPI_REG_OP_EXT_STIG_LSB, - f_pdata->dtr); + status = cqspi_enable_dtr(f_pdata, op, CQSPI_REG_OP_EXT_STIG_LSB); if (status) return status; @@ -565,17 +503,17 @@ static int cqspi_command_read(struct cqspi_flash_pdata *f_pdata, return -EINVAL; } - if (f_pdata->dtr) + if (op->cmd.dtr) opcode = op->cmd.opcode >> 8; else opcode = op->cmd.opcode; reg = opcode << CQSPI_REG_CMDCTRL_OPCODE_LSB; - rdreg = cqspi_calc_rdreg(f_pdata); + rdreg = cqspi_calc_rdreg(op); writel(rdreg, reg_base + CQSPI_REG_RD_INSTR); - dummy_clk = cqspi_calc_dummy(op, f_pdata->dtr); + dummy_clk = cqspi_calc_dummy(op); if (dummy_clk > CQSPI_DUMMY_CLKS_MAX) return -EOPNOTSUPP; @@ -622,12 +560,7 @@ static int cqspi_command_write(struct cqspi_flash_pdata *f_pdata, size_t write_len; int ret; - ret = cqspi_set_protocol(f_pdata, op); - if (ret) - return ret; - - ret = cqspi_enable_dtr(f_pdata, op, CQSPI_REG_OP_EXT_STIG_LSB, - f_pdata->dtr); + ret = cqspi_enable_dtr(f_pdata, op, CQSPI_REG_OP_EXT_STIG_LSB); if (ret) return ret; @@ -638,10 +571,10 @@ static int cqspi_command_write(struct cqspi_flash_pdata *f_pdata, return -EINVAL; } - reg = cqspi_calc_rdreg(f_pdata); + reg = cqspi_calc_rdreg(op); writel(reg, reg_base + CQSPI_REG_RD_INSTR); - if (f_pdata->dtr) + if (op->cmd.dtr) opcode = op->cmd.opcode >> 8; else opcode = op->cmd.opcode; @@ -688,21 +621,20 @@ static int cqspi_read_setup(struct cqspi_flash_pdata *f_pdata, int ret; u8 opcode; - ret = cqspi_enable_dtr(f_pdata, op, CQSPI_REG_OP_EXT_READ_LSB, - f_pdata->dtr); + ret = cqspi_enable_dtr(f_pdata, op, CQSPI_REG_OP_EXT_READ_LSB); if (ret) return ret; - if (f_pdata->dtr) + if (op->cmd.dtr) opcode = op->cmd.opcode >> 8; else opcode = op->cmd.opcode; reg = opcode << CQSPI_REG_RD_INSTR_OPCODE_LSB; - reg |= cqspi_calc_rdreg(f_pdata); + reg |= cqspi_calc_rdreg(op); /* Setup dummy clock cycles */ - dummy_clk = cqspi_calc_dummy(op, f_pdata->dtr); + dummy_clk = cqspi_calc_dummy(op); if (dummy_clk > CQSPI_DUMMY_CLKS_MAX) return -EOPNOTSUPP; @@ -947,22 +879,21 @@ static int cqspi_write_setup(struct cqspi_flash_pdata *f_pdata, void __iomem *reg_base = cqspi->iobase; u8 opcode; - ret = cqspi_enable_dtr(f_pdata, op, CQSPI_REG_OP_EXT_WRITE_LSB, - f_pdata->dtr); + ret = cqspi_enable_dtr(f_pdata, op, CQSPI_REG_OP_EXT_WRITE_LSB); if (ret) return ret; - if (f_pdata->dtr) + if (op->cmd.dtr) opcode = op->cmd.opcode >> 8; else opcode = op->cmd.opcode; /* Set opcode. */ reg = opcode << CQSPI_REG_WR_INSTR_OPCODE_LSB; - reg |= f_pdata->data_width << CQSPI_REG_WR_INSTR_TYPE_DATA_LSB; - reg |= f_pdata->addr_width << CQSPI_REG_WR_INSTR_TYPE_ADDR_LSB; + reg |= CQSPI_OP_WIDTH(op->data) << CQSPI_REG_WR_INSTR_TYPE_DATA_LSB; + reg |= CQSPI_OP_WIDTH(op->addr) << CQSPI_REG_WR_INSTR_TYPE_ADDR_LSB; writel(reg, reg_base + CQSPI_REG_WR_INSTR); - reg = cqspi_calc_rdreg(f_pdata); + reg = cqspi_calc_rdreg(op); writel(reg, reg_base + CQSPI_REG_RD_INSTR); /* @@ -1244,10 +1175,6 @@ static ssize_t cqspi_write(struct cqspi_flash_pdata *f_pdata, const u_char *buf = op->data.buf.out; int ret; - ret = cqspi_set_protocol(f_pdata, op); - if (ret) - return ret; - ret = cqspi_write_setup(f_pdata, op); if (ret) return ret; @@ -1260,7 +1187,7 @@ static ssize_t cqspi_write(struct cqspi_flash_pdata *f_pdata, * mode. So, we can not use direct mode when in DTR mode for writing * data. */ - if (!f_pdata->dtr && cqspi->use_direct_mode && + if (!op->cmd.dtr && cqspi->use_direct_mode && ((to + len) <= cqspi->ahb_size)) { memcpy_toio(cqspi->ahb_base + to, buf, len); return cqspi_wait_idle(cqspi); @@ -1348,9 +1275,6 @@ static ssize_t cqspi_read(struct cqspi_flash_pdata *f_pdata, int ret; ddata = of_device_get_match_data(dev); - ret = cqspi_set_protocol(f_pdata, op); - if (ret) - return ret; ret = cqspi_read_setup(f_pdata, op); if (ret) @@ -1423,13 +1347,7 @@ static bool cqspi_supports_mem_op(struct spi_mem *mem, return false; if (op->data.nbytes && op->data.buswidth != 8) return false; - } else if (all_false) { - /* Only 1-1-X ops are supported without DTR */ - if (op->cmd.nbytes && op->cmd.buswidth > 1) - return false; - if (op->addr.nbytes && op->addr.buswidth > 1) - return false; - } else { + } else if (!all_false) { /* Mixed DTR modes are not supported. */ return false; } @@ -1563,6 +1481,7 @@ static int cqspi_request_mmap_dma(struct cqspi_st *cqspi) cqspi->rx_chan = dma_request_chan_by_mask(&mask); if (IS_ERR(cqspi->rx_chan)) { int ret = PTR_ERR(cqspi->rx_chan); + cqspi->rx_chan = NULL; return dev_err_probe(&cqspi->pdev->dev, ret, "No Rx DMA available\n"); } @@ -1639,7 +1558,7 @@ static int cqspi_probe(struct platform_device *pdev) int ret; int irq; - master = spi_alloc_master(&pdev->dev, sizeof(*cqspi)); + master = devm_spi_alloc_master(&pdev->dev, sizeof(*cqspi)); if (!master) { dev_err(&pdev->dev, "spi_alloc_master failed\n"); return -ENOMEM; @@ -1652,6 +1571,7 @@ static int cqspi_probe(struct platform_device *pdev) cqspi = spi_master_get_devdata(master); cqspi->pdev = pdev; + cqspi->master = master; platform_set_drvdata(pdev, cqspi); /* Obtain configuration from OF. */ @@ -1700,11 +1620,9 @@ static int cqspi_probe(struct platform_device *pdev) } pm_runtime_enable(dev); - ret = pm_runtime_get_sync(dev); - if (ret < 0) { - pm_runtime_put_noidle(dev); + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) goto probe_master_put; - } ret = clk_prepare_enable(cqspi->clk); if (ret) { @@ -1784,7 +1702,7 @@ static int cqspi_probe(struct platform_device *pdev) goto probe_setup_failed; } - ret = devm_spi_register_master(dev, master); + ret = spi_register_master(master); if (ret) { dev_err(&pdev->dev, "failed to register SPI ctlr %d\n", ret); goto probe_setup_failed; @@ -1807,6 +1725,7 @@ static int cqspi_remove(struct platform_device *pdev) { struct cqspi_st *cqspi = platform_get_drvdata(pdev); + spi_unregister_master(cqspi->master); cqspi_controller_enable(cqspi, 0); if (cqspi->rx_chan) @@ -1865,7 +1784,7 @@ static const struct cqspi_driver_platdata intel_lgm_qspi = { }; static const struct cqspi_driver_platdata socfpga_qspi = { - .quirks = CQSPI_NO_SUPPORT_WR_COMPLETION, + .quirks = CQSPI_DISABLE_DAC_MODE | CQSPI_NO_SUPPORT_WR_COMPLETION, }; static const struct cqspi_driver_platdata versal_ospi = { @@ -1894,11 +1813,11 @@ static const struct of_device_id cqspi_dt_ids[] = { }, { .compatible = "xlnx,versal-ospi-1.0", - .data = (void *)&versal_ospi, + .data = &versal_ospi, }, { .compatible = "intel,socfpga-qspi", - .data = (void *)&socfpga_qspi, + .data = &socfpga_qspi, }, { /* end of table */ } }; diff --git a/drivers/spi/spi-cadence.c b/drivers/spi/spi-cadence.c index ceb16e70d235..a23d4f6329f5 100644 --- a/drivers/spi/spi-cadence.c +++ b/drivers/spi/spi-cadence.c @@ -342,7 +342,8 @@ static irqreturn_t cdns_spi_irq(int irq, void *dev_id) { struct spi_master *master = dev_id; struct cdns_spi *xspi = spi_master_get_devdata(master); - u32 intr_status, status; + irqreturn_t status; + u32 intr_status; status = IRQ_NONE; intr_status = cdns_spi_read(xspi, CDNS_SPI_ISR); @@ -657,7 +658,7 @@ static int __maybe_unused cdns_spi_resume(struct device *dev) * * Return: 0 on success and error value on error */ -static int __maybe_unused cnds_runtime_resume(struct device *dev) +static int __maybe_unused cdns_spi_runtime_resume(struct device *dev) { struct spi_master *master = dev_get_drvdata(dev); struct cdns_spi *xspi = spi_master_get_devdata(master); @@ -686,7 +687,7 @@ static int __maybe_unused cnds_runtime_resume(struct device *dev) * * Return: Always 0 */ -static int __maybe_unused cnds_runtime_suspend(struct device *dev) +static int __maybe_unused cdns_spi_runtime_suspend(struct device *dev) { struct spi_master *master = dev_get_drvdata(dev); struct cdns_spi *xspi = spi_master_get_devdata(master); @@ -698,8 +699,8 @@ static int __maybe_unused cnds_runtime_suspend(struct device *dev) } static const struct dev_pm_ops cdns_spi_dev_pm_ops = { - SET_RUNTIME_PM_OPS(cnds_runtime_suspend, - cnds_runtime_resume, NULL) + SET_RUNTIME_PM_OPS(cdns_spi_runtime_suspend, + cdns_spi_runtime_resume, NULL) SET_SYSTEM_SLEEP_PM_OPS(cdns_spi_suspend, cdns_spi_resume) }; diff --git a/drivers/spi/spi-clps711x.c b/drivers/spi/spi-clps711x.c index 0bef5ce08094..c005ed26a3e1 100644 --- a/drivers/spi/spi-clps711x.c +++ b/drivers/spi/spi-clps711x.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -89,6 +90,7 @@ static irqreturn_t spi_clps711x_isr(int irq, void *dev_id) static int spi_clps711x_probe(struct platform_device *pdev) { + struct device_node *np = pdev->dev.of_node; struct spi_clps711x_data *hw; struct spi_master *master; int irq, ret; @@ -117,8 +119,7 @@ static int spi_clps711x_probe(struct platform_device *pdev) goto err_out; } - hw->syscon = - syscon_regmap_lookup_by_compatible("cirrus,ep7209-syscon3"); + hw->syscon = syscon_regmap_lookup_by_phandle(np, "syscon"); if (IS_ERR(hw->syscon)) { ret = PTR_ERR(hw->syscon); goto err_out; diff --git a/drivers/spi/spi-fsl-qspi.c b/drivers/spi/spi-fsl-qspi.c index 9851551ebbe0..46ae46a944c5 100644 --- a/drivers/spi/spi-fsl-qspi.c +++ b/drivers/spi/spi-fsl-qspi.c @@ -876,6 +876,10 @@ static int fsl_qspi_probe(struct platform_device *pdev) res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "QuadSPI-memory"); + if (!res) { + ret = -EINVAL; + goto err_put_ctrl; + } q->memmap_phy = res->start; /* Since there are 4 cs, map size required is 4 times ahb_buf_size */ q->ahb_addr = devm_ioremap(dev, q->memmap_phy, diff --git a/drivers/spi/spi-img-spfi.c b/drivers/spi/spi-img-spfi.c index 5f05d519fbbd..71376b6df89d 100644 --- a/drivers/spi/spi-img-spfi.c +++ b/drivers/spi/spi-img-spfi.c @@ -731,7 +731,7 @@ static int img_spfi_resume(struct device *dev) int ret; ret = pm_runtime_get_sync(dev); - if (ret) { + if (ret < 0) { pm_runtime_put_noidle(dev); return ret; } diff --git a/drivers/spi/spi-imx.c b/drivers/spi/spi-imx.c index b2dd0a4d2446..bc97337fddf5 100644 --- a/drivers/spi/spi-imx.c +++ b/drivers/spi/spi-imx.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -32,6 +31,12 @@ static bool use_dma = true; module_param(use_dma, bool, 0644); MODULE_PARM_DESC(use_dma, "Enable usage of DMA when available (default)"); +/* define polling limits */ +static unsigned int polling_limit_us = 30; +module_param(polling_limit_us, uint, 0664); +MODULE_PARM_DESC(polling_limit_us, + "time in us to run a transfer in polling mode\n"); + #define MXC_RPM_TIMEOUT 2000 /* 2000ms */ #define MXC_CSPIRXDATA 0x00 @@ -64,15 +69,15 @@ enum spi_imx_devtype { struct spi_imx_data; struct spi_imx_devtype_data { - void (*intctrl)(struct spi_imx_data *, int); - int (*prepare_message)(struct spi_imx_data *, struct spi_message *); - int (*prepare_transfer)(struct spi_imx_data *, struct spi_device *); - void (*trigger)(struct spi_imx_data *); - int (*rx_available)(struct spi_imx_data *); - void (*reset)(struct spi_imx_data *); - void (*setup_wml)(struct spi_imx_data *); - void (*disable)(struct spi_imx_data *); - void (*disable_dma)(struct spi_imx_data *); + void (*intctrl)(struct spi_imx_data *spi_imx, int enable); + int (*prepare_message)(struct spi_imx_data *spi_imx, struct spi_message *msg); + int (*prepare_transfer)(struct spi_imx_data *spi_imx, struct spi_device *spi); + void (*trigger)(struct spi_imx_data *spi_imx); + int (*rx_available)(struct spi_imx_data *spi_imx); + void (*reset)(struct spi_imx_data *spi_imx); + void (*setup_wml)(struct spi_imx_data *spi_imx); + void (*disable)(struct spi_imx_data *spi_imx); + void (*disable_dma)(struct spi_imx_data *spi_imx); bool has_dmamode; bool has_slavemode; unsigned int fifo_size; @@ -86,7 +91,7 @@ struct spi_imx_devtype_data { }; struct spi_imx_data { - struct spi_bitbang bitbang; + struct spi_controller *controller; struct device *dev; struct completion xfer_done; @@ -102,12 +107,13 @@ struct spi_imx_data { unsigned int spi_drctl; unsigned int count, remainder; - void (*tx)(struct spi_imx_data *); - void (*rx)(struct spi_imx_data *); + void (*tx)(struct spi_imx_data *spi_imx); + void (*rx)(struct spi_imx_data *spi_imx); void *rx_buf; const void *tx_buf; unsigned int txfifo; /* number of words pushed in tx FIFO */ unsigned int dynamic_burst; + bool rx_only; /* Slave mode */ bool slave_mode; @@ -225,15 +231,15 @@ static int spi_imx_bytes_per_word(const int bits_per_word) return 4; } -static bool spi_imx_can_dma(struct spi_master *master, struct spi_device *spi, +static bool spi_imx_can_dma(struct spi_controller *controller, struct spi_device *spi, struct spi_transfer *transfer) { - struct spi_imx_data *spi_imx = spi_master_get_devdata(master); + struct spi_imx_data *spi_imx = spi_controller_get_devdata(controller); - if (!use_dma || master->fallback) + if (!use_dma || controller->fallback) return false; - if (!master->dma_rx) + if (!controller->dma_rx) return false; if (spi_imx->slave_mode) @@ -289,17 +295,16 @@ static bool spi_imx_can_dma(struct spi_master *master, struct spi_device *spi, static void spi_imx_buf_rx_swap_u32(struct spi_imx_data *spi_imx) { unsigned int val = readl(spi_imx->base + MXC_CSPIRXDATA); -#ifdef __LITTLE_ENDIAN - unsigned int bytes_per_word; -#endif if (spi_imx->rx_buf) { #ifdef __LITTLE_ENDIAN + unsigned int bytes_per_word; + bytes_per_word = spi_imx_bytes_per_word(spi_imx->bits_per_word); if (bytes_per_word == 1) - val = cpu_to_be32(val); + swab32s(&val); else if (bytes_per_word == 2) - val = (val << 16) | (val >> 16); + swahw32s(&val); #endif *(u32 *)spi_imx->rx_buf = val; spi_imx->rx_buf += sizeof(u32); @@ -353,9 +358,9 @@ static void spi_imx_buf_tx_swap_u32(struct spi_imx_data *spi_imx) bytes_per_word = spi_imx_bytes_per_word(spi_imx->bits_per_word); if (bytes_per_word == 1) - val = cpu_to_be32(val); + swab32s(&val); else if (bytes_per_word == 2) - val = (val << 16) | (val >> 16); + swahw32s(&val); #endif writel(val, spi_imx->base + MXC_CSPITXDATA); } @@ -469,7 +474,7 @@ static unsigned int mx51_ecspi_clkdiv(struct spi_imx_data *spi_imx, static void mx51_ecspi_intctrl(struct spi_imx_data *spi_imx, int enable) { - unsigned val = 0; + unsigned int val = 0; if (enable & MXC_INT_TE) val |= MX51_ECSPI_INT_TEEN; @@ -515,6 +520,7 @@ static int mx51_ecspi_prepare_message(struct spi_imx_data *spi_imx, u32 min_speed_hz = ~0U; u32 testreg, delay; u32 cfg = readl(spi_imx->base + MX51_ECSPI_CONFIG); + u32 current_cfg = cfg; /* set Master or Slave mode */ if (spi_imx->slave_mode) @@ -554,11 +560,6 @@ static int mx51_ecspi_prepare_message(struct spi_imx_data *spi_imx, else cfg |= MX51_ECSPI_CONFIG_SBBCTRL(spi->chip_select); - if (spi->mode & SPI_CPHA) - cfg |= MX51_ECSPI_CONFIG_SCLKPHA(spi->chip_select); - else - cfg &= ~MX51_ECSPI_CONFIG_SCLKPHA(spi->chip_select); - if (spi->mode & SPI_CPOL) { cfg |= MX51_ECSPI_CONFIG_SCLKPOL(spi->chip_select); cfg |= MX51_ECSPI_CONFIG_SCLKCTL(spi->chip_select); @@ -572,6 +573,9 @@ static int mx51_ecspi_prepare_message(struct spi_imx_data *spi_imx, else cfg &= ~MX51_ECSPI_CONFIG_SSBPOL(spi->chip_select); + if (cfg == current_cfg) + return 0; + writel(cfg, spi_imx->base + MX51_ECSPI_CONFIG); /* @@ -585,7 +589,7 @@ static int mx51_ecspi_prepare_message(struct spi_imx_data *spi_imx, * the SPI communication as the device on the other end would consider * the change of SCLK polarity as a clock tick already. * - * Because spi_imx->spi_bus_clk is only set in bitbang prepare_message + * Because spi_imx->spi_bus_clk is only set in prepare_message * callback, iterate over all the transfers in spi_message, find the * one with lowest bus frequency, and use that bus frequency for the * delay calculation. In case all transfers have speed_hz == 0, then @@ -606,6 +610,24 @@ static int mx51_ecspi_prepare_message(struct spi_imx_data *spi_imx, return 0; } +static void mx51_configure_cpha(struct spi_imx_data *spi_imx, + struct spi_device *spi) +{ + bool cpha = (spi->mode & SPI_CPHA); + bool flip_cpha = (spi->mode & SPI_RX_CPHA_FLIP) && spi_imx->rx_only; + u32 cfg = readl(spi_imx->base + MX51_ECSPI_CONFIG); + + /* Flip cpha logical value iff flip_cpha */ + cpha ^= flip_cpha; + + if (cpha) + cfg |= MX51_ECSPI_CONFIG_SCLKPHA(spi->chip_select); + else + cfg &= ~MX51_ECSPI_CONFIG_SCLKPHA(spi->chip_select); + + writel(cfg, spi_imx->base + MX51_ECSPI_CONFIG); +} + static int mx51_ecspi_prepare_transfer(struct spi_imx_data *spi_imx, struct spi_device *spi) { @@ -627,6 +649,8 @@ static int mx51_ecspi_prepare_transfer(struct spi_imx_data *spi_imx, ctrl |= mx51_ecspi_clkdiv(spi_imx, spi_imx->spi_bus_clk, &clk); spi_imx->spi_bus_clk = clk; + mx51_configure_cpha(spi_imx, spi); + /* * ERR009165: work in XHC mode instead of SMC as PIO on the chips * before i.mx6ul. @@ -1153,12 +1177,12 @@ static irqreturn_t spi_imx_isr(int irq, void *dev_id) return IRQ_HANDLED; } -static int spi_imx_dma_configure(struct spi_master *master) +static int spi_imx_dma_configure(struct spi_controller *controller) { int ret; enum dma_slave_buswidth buswidth; struct dma_slave_config rx = {}, tx = {}; - struct spi_imx_data *spi_imx = spi_master_get_devdata(master); + struct spi_imx_data *spi_imx = spi_controller_get_devdata(controller); switch (spi_imx_bytes_per_word(spi_imx->bits_per_word)) { case 4: @@ -1178,7 +1202,7 @@ static int spi_imx_dma_configure(struct spi_master *master) tx.dst_addr = spi_imx->base_phys + MXC_CSPITXDATA; tx.dst_addr_width = buswidth; tx.dst_maxburst = spi_imx->wml; - ret = dmaengine_slave_config(master->dma_tx, &tx); + ret = dmaengine_slave_config(controller->dma_tx, &tx); if (ret) { dev_err(spi_imx->dev, "TX dma configuration failed with %d\n", ret); return ret; @@ -1188,7 +1212,7 @@ static int spi_imx_dma_configure(struct spi_master *master) rx.src_addr = spi_imx->base_phys + MXC_CSPIRXDATA; rx.src_addr_width = buswidth; rx.src_maxburst = spi_imx->wml; - ret = dmaengine_slave_config(master->dma_rx, &rx); + ret = dmaengine_slave_config(controller->dma_rx, &rx); if (ret) { dev_err(spi_imx->dev, "RX dma configuration failed with %d\n", ret); return ret; @@ -1200,7 +1224,7 @@ static int spi_imx_dma_configure(struct spi_master *master) static int spi_imx_setupxfer(struct spi_device *spi, struct spi_transfer *t) { - struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master); + struct spi_imx_data *spi_imx = spi_controller_get_devdata(spi->controller); if (!t) return 0; @@ -1246,11 +1270,14 @@ static int spi_imx_setupxfer(struct spi_device *spi, spi_imx->dynamic_burst = 0; } - if (spi_imx_can_dma(spi_imx->bitbang.master, spi, t)) + if (spi_imx_can_dma(spi_imx->controller, spi, t)) spi_imx->usedma = true; else spi_imx->usedma = false; + spi_imx->rx_only = ((t->tx_buf == NULL) + || (t->tx_buf == spi->controller->dummy_tx)); + if (is_imx53_ecspi(spi_imx) && spi_imx->slave_mode) { spi_imx->rx = mx53_ecspi_rx_slave; spi_imx->tx = mx53_ecspi_tx_slave; @@ -1264,50 +1291,50 @@ static int spi_imx_setupxfer(struct spi_device *spi, static void spi_imx_sdma_exit(struct spi_imx_data *spi_imx) { - struct spi_master *master = spi_imx->bitbang.master; + struct spi_controller *controller = spi_imx->controller; - if (master->dma_rx) { - dma_release_channel(master->dma_rx); - master->dma_rx = NULL; + if (controller->dma_rx) { + dma_release_channel(controller->dma_rx); + controller->dma_rx = NULL; } - if (master->dma_tx) { - dma_release_channel(master->dma_tx); - master->dma_tx = NULL; + if (controller->dma_tx) { + dma_release_channel(controller->dma_tx); + controller->dma_tx = NULL; } } static int spi_imx_sdma_init(struct device *dev, struct spi_imx_data *spi_imx, - struct spi_master *master) + struct spi_controller *controller) { int ret; spi_imx->wml = spi_imx->devtype_data->fifo_size / 2; /* Prepare for TX DMA: */ - master->dma_tx = dma_request_chan(dev, "tx"); - if (IS_ERR(master->dma_tx)) { - ret = PTR_ERR(master->dma_tx); + controller->dma_tx = dma_request_chan(dev, "tx"); + if (IS_ERR(controller->dma_tx)) { + ret = PTR_ERR(controller->dma_tx); dev_dbg(dev, "can't get the TX DMA channel, error %d!\n", ret); - master->dma_tx = NULL; + controller->dma_tx = NULL; goto err; } /* Prepare for RX : */ - master->dma_rx = dma_request_chan(dev, "rx"); - if (IS_ERR(master->dma_rx)) { - ret = PTR_ERR(master->dma_rx); + controller->dma_rx = dma_request_chan(dev, "rx"); + if (IS_ERR(controller->dma_rx)) { + ret = PTR_ERR(controller->dma_rx); dev_dbg(dev, "can't get the RX DMA channel, error %d\n", ret); - master->dma_rx = NULL; + controller->dma_rx = NULL; goto err; } init_completion(&spi_imx->dma_rx_completion); init_completion(&spi_imx->dma_tx_completion); - master->can_dma = spi_imx_can_dma; - master->max_dma_len = MAX_SDMA_BD_BYTES; - spi_imx->bitbang.master->flags = SPI_MASTER_MUST_RX | - SPI_MASTER_MUST_TX; + controller->can_dma = spi_imx_can_dma; + controller->max_dma_len = MAX_SDMA_BD_BYTES; + spi_imx->controller->flags = SPI_CONTROLLER_MUST_RX | + SPI_CONTROLLER_MUST_TX; return 0; err: @@ -1349,7 +1376,7 @@ static int spi_imx_dma_transfer(struct spi_imx_data *spi_imx, struct dma_async_tx_descriptor *desc_tx, *desc_rx; unsigned long transfer_timeout; unsigned long timeout; - struct spi_master *master = spi_imx->bitbang.master; + struct spi_controller *controller = spi_imx->controller; struct sg_table *tx = &transfer->tx_sg, *rx = &transfer->rx_sg; struct scatterlist *last_sg = sg_last(rx->sgl, rx->nents); unsigned int bytes_per_word, i; @@ -1367,7 +1394,7 @@ static int spi_imx_dma_transfer(struct spi_imx_data *spi_imx, spi_imx->wml = i; - ret = spi_imx_dma_configure(master); + ret = spi_imx_dma_configure(controller); if (ret) goto dma_failure_no_start; @@ -1382,7 +1409,7 @@ static int spi_imx_dma_transfer(struct spi_imx_data *spi_imx, * The TX DMA setup starts the transfer, so make sure RX is configured * before TX. */ - desc_rx = dmaengine_prep_slave_sg(master->dma_rx, + desc_rx = dmaengine_prep_slave_sg(controller->dma_rx, rx->sgl, rx->nents, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); if (!desc_rx) { @@ -1394,14 +1421,14 @@ static int spi_imx_dma_transfer(struct spi_imx_data *spi_imx, desc_rx->callback_param = (void *)spi_imx; dmaengine_submit(desc_rx); reinit_completion(&spi_imx->dma_rx_completion); - dma_async_issue_pending(master->dma_rx); + dma_async_issue_pending(controller->dma_rx); - desc_tx = dmaengine_prep_slave_sg(master->dma_tx, + desc_tx = dmaengine_prep_slave_sg(controller->dma_tx, tx->sgl, tx->nents, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); if (!desc_tx) { - dmaengine_terminate_all(master->dma_tx); - dmaengine_terminate_all(master->dma_rx); + dmaengine_terminate_all(controller->dma_tx); + dmaengine_terminate_all(controller->dma_rx); return -EINVAL; } @@ -1409,7 +1436,7 @@ static int spi_imx_dma_transfer(struct spi_imx_data *spi_imx, desc_tx->callback_param = (void *)spi_imx; dmaengine_submit(desc_tx); reinit_completion(&spi_imx->dma_tx_completion); - dma_async_issue_pending(master->dma_tx); + dma_async_issue_pending(controller->dma_tx); transfer_timeout = spi_imx_calculate_timeout(spi_imx, transfer->len); @@ -1418,21 +1445,21 @@ static int spi_imx_dma_transfer(struct spi_imx_data *spi_imx, transfer_timeout); if (!timeout) { dev_err(spi_imx->dev, "I/O Error in DMA TX\n"); - dmaengine_terminate_all(master->dma_tx); - dmaengine_terminate_all(master->dma_rx); + dmaengine_terminate_all(controller->dma_tx); + dmaengine_terminate_all(controller->dma_rx); return -ETIMEDOUT; } timeout = wait_for_completion_timeout(&spi_imx->dma_rx_completion, transfer_timeout); if (!timeout) { - dev_err(&master->dev, "I/O Error in DMA RX\n"); + dev_err(&controller->dev, "I/O Error in DMA RX\n"); spi_imx->devtype_data->reset(spi_imx); - dmaengine_terminate_all(master->dma_rx); + dmaengine_terminate_all(controller->dma_rx); return -ETIMEDOUT; } - return transfer->len; + return 0; /* fallback to pio */ dma_failure_no_start: transfer->error |= SPI_TRANS_FAIL_NO_START; @@ -1442,7 +1469,7 @@ dma_failure_no_start: static int spi_imx_pio_transfer(struct spi_device *spi, struct spi_transfer *transfer) { - struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master); + struct spi_imx_data *spi_imx = spi_controller_get_devdata(spi->controller); unsigned long transfer_timeout; unsigned long timeout; @@ -1468,14 +1495,62 @@ static int spi_imx_pio_transfer(struct spi_device *spi, return -ETIMEDOUT; } - return transfer->len; + return 0; +} + +static int spi_imx_poll_transfer(struct spi_device *spi, + struct spi_transfer *transfer) +{ + struct spi_imx_data *spi_imx = spi_controller_get_devdata(spi->controller); + unsigned long timeout; + + spi_imx->tx_buf = transfer->tx_buf; + spi_imx->rx_buf = transfer->rx_buf; + spi_imx->count = transfer->len; + spi_imx->txfifo = 0; + spi_imx->remainder = 0; + + /* fill in the fifo before timeout calculations if we are + * interrupted here, then the data is getting transferred by + * the HW while we are interrupted + */ + spi_imx_push(spi_imx); + + timeout = spi_imx_calculate_timeout(spi_imx, transfer->len) + jiffies; + while (spi_imx->txfifo) { + /* RX */ + while (spi_imx->txfifo && + spi_imx->devtype_data->rx_available(spi_imx)) { + spi_imx->rx(spi_imx); + spi_imx->txfifo--; + } + + /* TX */ + if (spi_imx->count) { + spi_imx_push(spi_imx); + continue; + } + + if (spi_imx->txfifo && + time_after(jiffies, timeout)) { + + dev_err_ratelimited(&spi->dev, + "timeout period reached: jiffies: %lu- falling back to interrupt mode\n", + jiffies - timeout); + + /* fall back to interrupt mode */ + return spi_imx_pio_transfer(spi, transfer); + } + } + + return 0; } static int spi_imx_pio_transfer_slave(struct spi_device *spi, struct spi_transfer *transfer) { - struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master); - int ret = transfer->len; + struct spi_imx_data *spi_imx = spi_controller_get_devdata(spi->controller); + int ret = 0; if (is_imx53_ecspi(spi_imx) && transfer->len > MX53_MAX_TRANSFER_BYTES) { @@ -1515,11 +1590,14 @@ static int spi_imx_pio_transfer_slave(struct spi_device *spi, return ret; } -static int spi_imx_transfer(struct spi_device *spi, +static int spi_imx_transfer_one(struct spi_controller *controller, + struct spi_device *spi, struct spi_transfer *transfer) { - struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master); + struct spi_imx_data *spi_imx = spi_controller_get_devdata(spi->controller); + unsigned long hz_per_byte, byte_limit; + spi_imx_setupxfer(spi, transfer); transfer->effective_speed_hz = spi_imx->spi_bus_clk; /* flush rxfifo before transfer */ @@ -1529,6 +1607,17 @@ static int spi_imx_transfer(struct spi_device *spi, if (spi_imx->slave_mode) return spi_imx_pio_transfer_slave(spi, transfer); + /* + * Calculate the estimated time in us the transfer runs. Find + * the number of Hz per byte per polling limit. + */ + hz_per_byte = polling_limit_us ? ((8 + 4) * USEC_PER_SEC) / polling_limit_us : 0; + byte_limit = hz_per_byte ? transfer->effective_speed_hz / hz_per_byte : 1; + + /* run in polling mode for short transfers */ + if (transfer->len < byte_limit) + return spi_imx_poll_transfer(spi, transfer); + if (spi_imx->usedma) return spi_imx_dma_transfer(spi_imx, transfer); @@ -1548,14 +1637,13 @@ static void spi_imx_cleanup(struct spi_device *spi) } static int -spi_imx_prepare_message(struct spi_master *master, struct spi_message *msg) +spi_imx_prepare_message(struct spi_controller *controller, struct spi_message *msg) { - struct spi_imx_data *spi_imx = spi_master_get_devdata(master); + struct spi_imx_data *spi_imx = spi_controller_get_devdata(controller); int ret; - ret = pm_runtime_get_sync(spi_imx->dev); + ret = pm_runtime_resume_and_get(spi_imx->dev); if (ret < 0) { - pm_runtime_put_noidle(spi_imx->dev); dev_err(spi_imx->dev, "failed to enable clock\n"); return ret; } @@ -1570,18 +1658,18 @@ spi_imx_prepare_message(struct spi_master *master, struct spi_message *msg) } static int -spi_imx_unprepare_message(struct spi_master *master, struct spi_message *msg) +spi_imx_unprepare_message(struct spi_controller *controller, struct spi_message *msg) { - struct spi_imx_data *spi_imx = spi_master_get_devdata(master); + struct spi_imx_data *spi_imx = spi_controller_get_devdata(controller); pm_runtime_mark_last_busy(spi_imx->dev); pm_runtime_put_autosuspend(spi_imx->dev); return 0; } -static int spi_imx_slave_abort(struct spi_master *master) +static int spi_imx_slave_abort(struct spi_controller *controller) { - struct spi_imx_data *spi_imx = spi_master_get_devdata(master); + struct spi_imx_data *spi_imx = spi_controller_get_devdata(controller); spi_imx->slave_aborted = true; complete(&spi_imx->xfer_done); @@ -1592,7 +1680,7 @@ static int spi_imx_slave_abort(struct spi_master *master) static int spi_imx_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; - struct spi_master *master; + struct spi_controller *controller; struct spi_imx_data *spi_imx; struct resource *res; int ret, irq, spi_drctl; @@ -1604,12 +1692,12 @@ static int spi_imx_probe(struct platform_device *pdev) slave_mode = devtype_data->has_slavemode && of_property_read_bool(np, "spi-slave"); if (slave_mode) - master = spi_alloc_slave(&pdev->dev, - sizeof(struct spi_imx_data)); + controller = spi_alloc_slave(&pdev->dev, + sizeof(struct spi_imx_data)); else - master = spi_alloc_master(&pdev->dev, - sizeof(struct spi_imx_data)); - if (!master) + controller = spi_alloc_master(&pdev->dev, + sizeof(struct spi_imx_data)); + if (!controller) return -ENOMEM; ret = of_property_read_u32(np, "fsl,spi-rdy-drctl", &spi_drctl); @@ -1618,14 +1706,14 @@ static int spi_imx_probe(struct platform_device *pdev) spi_drctl = 0; } - platform_set_drvdata(pdev, master); + platform_set_drvdata(pdev, controller); - master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32); - master->bus_num = np ? -1 : pdev->id; - master->use_gpio_descriptors = true; + controller->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32); + controller->bus_num = np ? -1 : pdev->id; + controller->use_gpio_descriptors = true; - spi_imx = spi_master_get_devdata(master); - spi_imx->bitbang.master = master; + spi_imx = spi_controller_get_devdata(controller); + spi_imx->controller = controller; spi_imx->dev = &pdev->dev; spi_imx->slave_mode = slave_mode; @@ -1638,22 +1726,24 @@ static int spi_imx_probe(struct platform_device *pdev) * board files have <= 3 chip selects. */ if (!device_property_read_u32(&pdev->dev, "num-cs", &val)) - master->num_chipselect = val; + controller->num_chipselect = val; else - master->num_chipselect = 3; + controller->num_chipselect = 3; + + spi_imx->controller->transfer_one = spi_imx_transfer_one; + spi_imx->controller->setup = spi_imx_setup; + spi_imx->controller->cleanup = spi_imx_cleanup; + spi_imx->controller->prepare_message = spi_imx_prepare_message; + spi_imx->controller->unprepare_message = spi_imx_unprepare_message; + spi_imx->controller->slave_abort = spi_imx_slave_abort; + spi_imx->controller->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_NO_CS; - spi_imx->bitbang.setup_transfer = spi_imx_setupxfer; - spi_imx->bitbang.txrx_bufs = spi_imx_transfer; - spi_imx->bitbang.master->setup = spi_imx_setup; - spi_imx->bitbang.master->cleanup = spi_imx_cleanup; - spi_imx->bitbang.master->prepare_message = spi_imx_prepare_message; - spi_imx->bitbang.master->unprepare_message = spi_imx_unprepare_message; - spi_imx->bitbang.master->slave_abort = spi_imx_slave_abort; - spi_imx->bitbang.master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH \ - | SPI_NO_CS; if (is_imx35_cspi(spi_imx) || is_imx51_ecspi(spi_imx) || is_imx53_ecspi(spi_imx)) - spi_imx->bitbang.master->mode_bits |= SPI_LOOP | SPI_READY; + spi_imx->controller->mode_bits |= SPI_LOOP | SPI_READY; + + if (is_imx51_ecspi(spi_imx) || is_imx53_ecspi(spi_imx)) + spi_imx->controller->mode_bits |= SPI_RX_CPHA_FLIP; if (is_imx51_ecspi(spi_imx) && device_property_read_u32(&pdev->dev, "cs-gpios", NULL)) @@ -1662,7 +1752,7 @@ static int spi_imx_probe(struct platform_device *pdev) * setting the burst length to the word size. This is * considerably faster than manually controlling the CS. */ - spi_imx->bitbang.master->mode_bits |= SPI_CS_WORD; + spi_imx->controller->mode_bits |= SPI_CS_WORD; spi_imx->spi_drctl = spi_drctl; @@ -1672,38 +1762,38 @@ static int spi_imx_probe(struct platform_device *pdev) spi_imx->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(spi_imx->base)) { ret = PTR_ERR(spi_imx->base); - goto out_master_put; + goto out_controller_put; } spi_imx->base_phys = res->start; irq = platform_get_irq(pdev, 0); if (irq < 0) { ret = irq; - goto out_master_put; + goto out_controller_put; } ret = devm_request_irq(&pdev->dev, irq, spi_imx_isr, 0, dev_name(&pdev->dev), spi_imx); if (ret) { dev_err(&pdev->dev, "can't get irq%d: %d\n", irq, ret); - goto out_master_put; + goto out_controller_put; } spi_imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); if (IS_ERR(spi_imx->clk_ipg)) { ret = PTR_ERR(spi_imx->clk_ipg); - goto out_master_put; + goto out_controller_put; } spi_imx->clk_per = devm_clk_get(&pdev->dev, "per"); if (IS_ERR(spi_imx->clk_per)) { ret = PTR_ERR(spi_imx->clk_per); - goto out_master_put; + goto out_controller_put; } ret = clk_prepare_enable(spi_imx->clk_per); if (ret) - goto out_master_put; + goto out_controller_put; ret = clk_prepare_enable(spi_imx->clk_ipg); if (ret) @@ -1721,7 +1811,7 @@ static int spi_imx_probe(struct platform_device *pdev) * if validated on other chips. */ if (spi_imx->devtype_data->has_dmamode) { - ret = spi_imx_sdma_init(&pdev->dev, spi_imx, master); + ret = spi_imx_sdma_init(&pdev->dev, spi_imx, controller); if (ret == -EPROBE_DEFER) goto out_runtime_pm_put; @@ -1734,11 +1824,11 @@ static int spi_imx_probe(struct platform_device *pdev) spi_imx->devtype_data->intctrl(spi_imx, 0); - master->dev.of_node = pdev->dev.of_node; - ret = spi_bitbang_start(&spi_imx->bitbang); + controller->dev.of_node = pdev->dev.of_node; + ret = spi_register_controller(controller); if (ret) { - dev_err_probe(&pdev->dev, ret, "bitbang start failed\n"); - goto out_bitbang_start; + dev_err_probe(&pdev->dev, ret, "register controller failed\n"); + goto out_register_controller; } pm_runtime_mark_last_busy(spi_imx->dev); @@ -1746,7 +1836,7 @@ static int spi_imx_probe(struct platform_device *pdev) return ret; -out_bitbang_start: +out_register_controller: if (spi_imx->devtype_data->has_dmamode) spi_imx_sdma_exit(spi_imx); out_runtime_pm_put: @@ -1757,23 +1847,22 @@ out_runtime_pm_put: clk_disable_unprepare(spi_imx->clk_ipg); out_put_per: clk_disable_unprepare(spi_imx->clk_per); -out_master_put: - spi_master_put(master); +out_controller_put: + spi_controller_put(controller); return ret; } static int spi_imx_remove(struct platform_device *pdev) { - struct spi_master *master = platform_get_drvdata(pdev); - struct spi_imx_data *spi_imx = spi_master_get_devdata(master); + struct spi_controller *controller = platform_get_drvdata(pdev); + struct spi_imx_data *spi_imx = spi_controller_get_devdata(controller); int ret; - spi_bitbang_stop(&spi_imx->bitbang); + spi_unregister_controller(controller); - ret = pm_runtime_get_sync(spi_imx->dev); + ret = pm_runtime_resume_and_get(spi_imx->dev); if (ret < 0) { - pm_runtime_put_noidle(spi_imx->dev); dev_err(spi_imx->dev, "failed to enable clock\n"); return ret; } @@ -1785,18 +1874,17 @@ static int spi_imx_remove(struct platform_device *pdev) pm_runtime_disable(spi_imx->dev); spi_imx_sdma_exit(spi_imx); - spi_master_put(master); return 0; } static int __maybe_unused spi_imx_runtime_resume(struct device *dev) { - struct spi_master *master = dev_get_drvdata(dev); + struct spi_controller *controller = dev_get_drvdata(dev); struct spi_imx_data *spi_imx; int ret; - spi_imx = spi_master_get_devdata(master); + spi_imx = spi_controller_get_devdata(controller); ret = clk_prepare_enable(spi_imx->clk_per); if (ret) @@ -1813,10 +1901,10 @@ static int __maybe_unused spi_imx_runtime_resume(struct device *dev) static int __maybe_unused spi_imx_runtime_suspend(struct device *dev) { - struct spi_master *master = dev_get_drvdata(dev); + struct spi_controller *controller = dev_get_drvdata(dev); struct spi_imx_data *spi_imx; - spi_imx = spi_master_get_devdata(master); + spi_imx = spi_controller_get_devdata(controller); clk_disable_unprepare(spi_imx->clk_per); clk_disable_unprepare(spi_imx->clk_ipg); diff --git a/drivers/spi/spi-ingenic.c b/drivers/spi/spi-ingenic.c index 03077a7e11c8..713a238bee63 100644 --- a/drivers/spi/spi-ingenic.c +++ b/drivers/spi/spi-ingenic.c @@ -1,8 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 /* - * SPI bus driver for the Ingenic JZ47xx SoCs + * SPI bus driver for the Ingenic SoCs * Copyright (c) 2017-2021 Artur Rojek * Copyright (c) 2017-2021 Paul Cercueil + * Copyright (c) 2022 周琰杰 (Zhou Yanjie) */ #include @@ -52,6 +53,9 @@ struct jz_soc_info { u32 bits_per_word_mask; struct reg_field flen_field; bool has_trendian; + + unsigned int max_speed_hz; + unsigned int max_native_cs; }; struct ingenic_spi { @@ -380,7 +384,7 @@ static int spi_ingenic_probe(struct platform_device *pdev) struct spi_controller *ctlr; struct ingenic_spi *priv; void __iomem *base; - int ret; + int num_cs, ret; pdata = of_device_get_match_data(dev); if (!pdata) { @@ -416,6 +420,9 @@ static int spi_ingenic_probe(struct platform_device *pdev) if (IS_ERR(priv->flen_field)) return PTR_ERR(priv->flen_field); + if (device_property_read_u32(dev, "num-cs", &num_cs)) + num_cs = pdata->max_native_cs; + platform_set_drvdata(pdev, ctlr); ctlr->prepare_transfer_hardware = spi_ingenic_prepare_hardware; @@ -428,8 +435,10 @@ static int spi_ingenic_probe(struct platform_device *pdev) ctlr->max_dma_len = SPI_INGENIC_FIFO_SIZE; ctlr->bits_per_word_mask = pdata->bits_per_word_mask; ctlr->min_speed_hz = 7200; - ctlr->max_speed_hz = 54000000; - ctlr->num_chipselect = 2; + ctlr->max_speed_hz = pdata->max_speed_hz; + ctlr->use_gpio_descriptors = true; + ctlr->max_native_cs = pdata->max_native_cs; + ctlr->num_chipselect = num_cs; ctlr->dev.of_node = pdev->dev.of_node; if (spi_ingenic_request_dma(ctlr, dev)) @@ -452,17 +461,44 @@ static const struct jz_soc_info jz4750_soc_info = { .bits_per_word_mask = SPI_BPW_RANGE_MASK(2, 17), .flen_field = REG_FIELD(REG_SSICR1, 4, 7), .has_trendian = false, + + .max_speed_hz = 54000000, + .max_native_cs = 2, }; static const struct jz_soc_info jz4780_soc_info = { .bits_per_word_mask = SPI_BPW_RANGE_MASK(2, 32), .flen_field = REG_FIELD(REG_SSICR1, 3, 7), .has_trendian = true, + + .max_speed_hz = 54000000, + .max_native_cs = 2, +}; + +static const struct jz_soc_info x1000_soc_info = { + .bits_per_word_mask = SPI_BPW_RANGE_MASK(2, 32), + .flen_field = REG_FIELD(REG_SSICR1, 3, 7), + .has_trendian = true, + + .max_speed_hz = 50000000, + .max_native_cs = 2, +}; + +static const struct jz_soc_info x2000_soc_info = { + .bits_per_word_mask = SPI_BPW_RANGE_MASK(2, 32), + .flen_field = REG_FIELD(REG_SSICR1, 3, 7), + .has_trendian = true, + + .max_speed_hz = 50000000, + .max_native_cs = 1, }; static const struct of_device_id spi_ingenic_of_match[] = { { .compatible = "ingenic,jz4750-spi", .data = &jz4750_soc_info }, + { .compatible = "ingenic,jz4775-spi", .data = &jz4780_soc_info }, { .compatible = "ingenic,jz4780-spi", .data = &jz4780_soc_info }, + { .compatible = "ingenic,x1000-spi", .data = &x1000_soc_info }, + { .compatible = "ingenic,x2000-spi", .data = &x2000_soc_info }, {} }; MODULE_DEVICE_TABLE(of, spi_ingenic_of_match); @@ -476,7 +512,8 @@ static struct platform_driver spi_ingenic_driver = { }; module_platform_driver(spi_ingenic_driver); -MODULE_DESCRIPTION("SPI bus driver for the Ingenic JZ47xx SoCs"); +MODULE_DESCRIPTION("SPI bus driver for the Ingenic SoCs"); MODULE_AUTHOR("Artur Rojek "); MODULE_AUTHOR("Paul Cercueil "); +MODULE_AUTHOR("周琰杰 (Zhou Yanjie) "); MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-intel.c b/drivers/spi/spi-intel.c index e937cfe85559..50f42983b950 100644 --- a/drivers/spi/spi-intel.c +++ b/drivers/spi/spi-intel.c @@ -779,10 +779,59 @@ static const char *intel_spi_get_name(struct spi_mem *mem) return dev_name(ispi->dev); } +static int intel_spi_dirmap_create(struct spi_mem_dirmap_desc *desc) +{ + struct intel_spi *ispi = spi_master_get_devdata(desc->mem->spi->master); + const struct intel_spi_mem_op *iop; + + iop = intel_spi_match_mem_op(ispi, &desc->info.op_tmpl); + if (!iop) + return -EOPNOTSUPP; + + desc->priv = (void *)iop; + return 0; +} + +static ssize_t intel_spi_dirmap_read(struct spi_mem_dirmap_desc *desc, u64 offs, + size_t len, void *buf) +{ + struct intel_spi *ispi = spi_master_get_devdata(desc->mem->spi->master); + const struct intel_spi_mem_op *iop = desc->priv; + struct spi_mem_op op = desc->info.op_tmpl; + int ret; + + /* Fill in the gaps */ + op.addr.val = offs; + op.data.nbytes = len; + op.data.buf.in = buf; + + ret = iop->exec_op(ispi, iop, &op); + return ret ? ret : len; +} + +static ssize_t intel_spi_dirmap_write(struct spi_mem_dirmap_desc *desc, u64 offs, + size_t len, const void *buf) +{ + struct intel_spi *ispi = spi_master_get_devdata(desc->mem->spi->master); + const struct intel_spi_mem_op *iop = desc->priv; + struct spi_mem_op op = desc->info.op_tmpl; + int ret; + + op.addr.val = offs; + op.data.nbytes = len; + op.data.buf.out = buf; + + ret = iop->exec_op(ispi, iop, &op); + return ret ? ret : len; +} + static const struct spi_controller_mem_ops intel_spi_mem_ops = { .supports_op = intel_spi_supports_mem_op, .exec_op = intel_spi_exec_mem_op, .get_name = intel_spi_get_name, + .dirmap_create = intel_spi_dirmap_create, + .dirmap_read = intel_spi_dirmap_read, + .dirmap_write = intel_spi_dirmap_write, }; #define INTEL_SPI_OP_ADDR(__nbytes) \ @@ -1205,7 +1254,7 @@ static int intel_spi_populate_chip(struct intel_spi *ispi) * intel_spi_probe() - Probe the Intel SPI flash controller * @dev: Pointer to the parent device * @mem: MMIO resource - * @info: Platform spefific information + * @info: Platform specific information * * Probes Intel SPI flash controller and creates the flash chip device. * Returns %0 on success and negative errno in case of failure. diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index 0e8dafc62d94..e8de4f5017cd 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "internals.h" @@ -211,6 +212,15 @@ static int spi_mem_check_op(const struct spi_mem_op *op) !spi_mem_buswidth_is_valid(op->data.buswidth)) return -EINVAL; + /* Buffers must be DMA-able. */ + if (WARN_ON_ONCE(op->data.dir == SPI_MEM_DATA_IN && + object_is_on_stack(op->data.buf.in))) + return -EINVAL; + + if (WARN_ON_ONCE(op->data.dir == SPI_MEM_DATA_OUT && + object_is_on_stack(op->data.buf.out))) + return -EINVAL; + return 0; } @@ -262,9 +272,8 @@ static int spi_mem_access_start(struct spi_mem *mem) if (ctlr->auto_runtime_pm) { int ret; - ret = pm_runtime_get_sync(ctlr->dev.parent); + ret = pm_runtime_resume_and_get(ctlr->dev.parent); if (ret < 0) { - pm_runtime_put_noidle(ctlr->dev.parent); dev_err(&ctlr->dev, "Failed to power device: %d\n", ret); return ret; diff --git a/drivers/spi/spi-mpc52xx-psc.c b/drivers/spi/spi-mpc52xx-psc.c index 21ef5d481faf..7654736c2c0e 100644 --- a/drivers/spi/spi-mpc52xx-psc.c +++ b/drivers/spi/spi-mpc52xx-psc.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include diff --git a/drivers/spi/spi-mpc52xx.c b/drivers/spi/spi-mpc52xx.c index 51041526546d..3ebdce804b90 100644 --- a/drivers/spi/spi-mpc52xx.c +++ b/drivers/spi/spi-mpc52xx.c @@ -19,6 +19,9 @@ #include #include #include +#include +#include + #include #include diff --git a/drivers/spi/spi-mt65xx.c b/drivers/spi/spi-mt65xx.c index 1a0b3208dfca..0a3b9f7eed30 100644 --- a/drivers/spi/spi-mt65xx.c +++ b/drivers/spi/spi-mt65xx.c @@ -17,105 +17,148 @@ #include #include #include +#include #include -#define SPI_CFG0_REG 0x0000 -#define SPI_CFG1_REG 0x0004 -#define SPI_TX_SRC_REG 0x0008 -#define SPI_RX_DST_REG 0x000c -#define SPI_TX_DATA_REG 0x0010 -#define SPI_RX_DATA_REG 0x0014 -#define SPI_CMD_REG 0x0018 -#define SPI_STATUS0_REG 0x001c -#define SPI_PAD_SEL_REG 0x0024 -#define SPI_CFG2_REG 0x0028 -#define SPI_TX_SRC_REG_64 0x002c -#define SPI_RX_DST_REG_64 0x0030 -#define SPI_CFG3_IPM_REG 0x0040 +#define SPI_CFG0_REG 0x0000 +#define SPI_CFG1_REG 0x0004 +#define SPI_TX_SRC_REG 0x0008 +#define SPI_RX_DST_REG 0x000c +#define SPI_TX_DATA_REG 0x0010 +#define SPI_RX_DATA_REG 0x0014 +#define SPI_CMD_REG 0x0018 +#define SPI_STATUS0_REG 0x001c +#define SPI_PAD_SEL_REG 0x0024 +#define SPI_CFG2_REG 0x0028 +#define SPI_TX_SRC_REG_64 0x002c +#define SPI_RX_DST_REG_64 0x0030 +#define SPI_CFG3_IPM_REG 0x0040 -#define SPI_CFG0_SCK_HIGH_OFFSET 0 -#define SPI_CFG0_SCK_LOW_OFFSET 8 -#define SPI_CFG0_CS_HOLD_OFFSET 16 -#define SPI_CFG0_CS_SETUP_OFFSET 24 -#define SPI_ADJUST_CFG0_CS_HOLD_OFFSET 0 -#define SPI_ADJUST_CFG0_CS_SETUP_OFFSET 16 +#define SPI_CFG0_SCK_HIGH_OFFSET 0 +#define SPI_CFG0_SCK_LOW_OFFSET 8 +#define SPI_CFG0_CS_HOLD_OFFSET 16 +#define SPI_CFG0_CS_SETUP_OFFSET 24 +#define SPI_ADJUST_CFG0_CS_HOLD_OFFSET 0 +#define SPI_ADJUST_CFG0_CS_SETUP_OFFSET 16 -#define SPI_CFG1_CS_IDLE_OFFSET 0 -#define SPI_CFG1_PACKET_LOOP_OFFSET 8 -#define SPI_CFG1_PACKET_LENGTH_OFFSET 16 -#define SPI_CFG1_GET_TICK_DLY_OFFSET 29 -#define SPI_CFG1_GET_TICK_DLY_OFFSET_V1 30 +#define SPI_CFG1_CS_IDLE_OFFSET 0 +#define SPI_CFG1_PACKET_LOOP_OFFSET 8 +#define SPI_CFG1_PACKET_LENGTH_OFFSET 16 +#define SPI_CFG1_GET_TICK_DLY_OFFSET 29 +#define SPI_CFG1_GET_TICK_DLY_OFFSET_V1 30 -#define SPI_CFG1_GET_TICK_DLY_MASK 0xe0000000 -#define SPI_CFG1_GET_TICK_DLY_MASK_V1 0xc0000000 +#define SPI_CFG1_GET_TICK_DLY_MASK 0xe0000000 +#define SPI_CFG1_GET_TICK_DLY_MASK_V1 0xc0000000 -#define SPI_CFG1_CS_IDLE_MASK 0xff -#define SPI_CFG1_PACKET_LOOP_MASK 0xff00 -#define SPI_CFG1_PACKET_LENGTH_MASK 0x3ff0000 -#define SPI_CFG1_IPM_PACKET_LENGTH_MASK GENMASK(31, 16) -#define SPI_CFG2_SCK_HIGH_OFFSET 0 -#define SPI_CFG2_SCK_LOW_OFFSET 16 +#define SPI_CFG1_CS_IDLE_MASK 0xff +#define SPI_CFG1_PACKET_LOOP_MASK 0xff00 +#define SPI_CFG1_PACKET_LENGTH_MASK 0x3ff0000 +#define SPI_CFG1_IPM_PACKET_LENGTH_MASK GENMASK(31, 16) +#define SPI_CFG2_SCK_HIGH_OFFSET 0 +#define SPI_CFG2_SCK_LOW_OFFSET 16 -#define SPI_CMD_ACT BIT(0) -#define SPI_CMD_RESUME BIT(1) -#define SPI_CMD_RST BIT(2) -#define SPI_CMD_PAUSE_EN BIT(4) -#define SPI_CMD_DEASSERT BIT(5) -#define SPI_CMD_SAMPLE_SEL BIT(6) -#define SPI_CMD_CS_POL BIT(7) -#define SPI_CMD_CPHA BIT(8) -#define SPI_CMD_CPOL BIT(9) -#define SPI_CMD_RX_DMA BIT(10) -#define SPI_CMD_TX_DMA BIT(11) -#define SPI_CMD_TXMSBF BIT(12) -#define SPI_CMD_RXMSBF BIT(13) -#define SPI_CMD_RX_ENDIAN BIT(14) -#define SPI_CMD_TX_ENDIAN BIT(15) -#define SPI_CMD_FINISH_IE BIT(16) -#define SPI_CMD_PAUSE_IE BIT(17) -#define SPI_CMD_IPM_NONIDLE_MODE BIT(19) -#define SPI_CMD_IPM_SPIM_LOOP BIT(21) -#define SPI_CMD_IPM_GET_TICKDLY_OFFSET 22 +#define SPI_CMD_ACT BIT(0) +#define SPI_CMD_RESUME BIT(1) +#define SPI_CMD_RST BIT(2) +#define SPI_CMD_PAUSE_EN BIT(4) +#define SPI_CMD_DEASSERT BIT(5) +#define SPI_CMD_SAMPLE_SEL BIT(6) +#define SPI_CMD_CS_POL BIT(7) +#define SPI_CMD_CPHA BIT(8) +#define SPI_CMD_CPOL BIT(9) +#define SPI_CMD_RX_DMA BIT(10) +#define SPI_CMD_TX_DMA BIT(11) +#define SPI_CMD_TXMSBF BIT(12) +#define SPI_CMD_RXMSBF BIT(13) +#define SPI_CMD_RX_ENDIAN BIT(14) +#define SPI_CMD_TX_ENDIAN BIT(15) +#define SPI_CMD_FINISH_IE BIT(16) +#define SPI_CMD_PAUSE_IE BIT(17) +#define SPI_CMD_IPM_NONIDLE_MODE BIT(19) +#define SPI_CMD_IPM_SPIM_LOOP BIT(21) +#define SPI_CMD_IPM_GET_TICKDLY_OFFSET 22 #define SPI_CMD_IPM_GET_TICKDLY_MASK GENMASK(24, 22) -#define SPI_CFG3_IPM_HALF_DUPLEX_DIR BIT(2) -#define SPI_CFG3_IPM_HALF_DUPLEX_EN BIT(3) -#define MT8173_SPI_MAX_PAD_SEL 3 -#define MTK_SPI_PAUSE_INT_STATUS 0x2 +#define PIN_MODE_CFG(x) ((x) / 2) -#define MTK_SPI_IDLE 0 -#define MTK_SPI_PAUSED 1 +#define SPI_CFG3_IPM_HALF_DUPLEX_DIR BIT(2) +#define SPI_CFG3_IPM_HALF_DUPLEX_EN BIT(3) +#define SPI_CFG3_IPM_XMODE_EN BIT(4) +#define SPI_CFG3_IPM_NODATA_FLAG BIT(5) +#define SPI_CFG3_IPM_CMD_BYTELEN_OFFSET 8 +#define SPI_CFG3_IPM_ADDR_BYTELEN_OFFSET 12 -#define MTK_SPI_MAX_FIFO_SIZE 32U -#define MTK_SPI_PACKET_SIZE 1024 -#define MTK_SPI_IPM_PACKET_SIZE SZ_64K -#define MTK_SPI_32BITS_MASK (0xffffffff) +#define SPI_CFG3_IPM_CMD_PIN_MODE_MASK GENMASK(1, 0) +#define SPI_CFG3_IPM_CMD_BYTELEN_MASK GENMASK(11, 8) +#define SPI_CFG3_IPM_ADDR_BYTELEN_MASK GENMASK(15, 12) -#define DMA_ADDR_EXT_BITS (36) -#define DMA_ADDR_DEF_BITS (32) +#define MT8173_SPI_MAX_PAD_SEL 3 +#define MTK_SPI_PAUSE_INT_STATUS 0x2 + +#define MTK_SPI_MAX_FIFO_SIZE 32U +#define MTK_SPI_PACKET_SIZE 1024 +#define MTK_SPI_IPM_PACKET_SIZE SZ_64K +#define MTK_SPI_IPM_PACKET_LOOP SZ_256 + +#define MTK_SPI_IDLE 0 +#define MTK_SPI_PAUSED 1 + +#define MTK_SPI_32BITS_MASK (0xffffffff) + +#define DMA_ADDR_EXT_BITS (36) +#define DMA_ADDR_DEF_BITS (32) + +/** + * struct mtk_spi_compatible - device data structure + * @need_pad_sel: Enable pad (pins) selection in SPI controller + * @must_tx: Must explicitly send dummy TX bytes to do RX only transfer + * @enhance_timing: Enable adjusting cfg register to enhance time accuracy + * @dma_ext: DMA address extension supported + * @no_need_unprepare: Don't unprepare the SPI clk during runtime + * @ipm_design: Adjust/extend registers to support IPM design IP features + */ struct mtk_spi_compatible { bool need_pad_sel; - /* Must explicitly send dummy Tx bytes to do Rx only transfer */ bool must_tx; - /* some IC design adjust cfg register to enhance time accuracy */ bool enhance_timing; - /* some IC support DMA addr extension */ bool dma_ext; - /* some IC no need unprepare SPI clk */ bool no_need_unprepare; - /* IPM design adjust and extend register to support more features */ bool ipm_design; - }; +/** + * struct mtk_spi - SPI driver instance + * @base: Start address of the SPI controller registers + * @state: SPI controller state + * @pad_num: Number of pad_sel entries + * @pad_sel: Groups of pins to select + * @parent_clk: Parent of sel_clk + * @sel_clk: SPI master mux clock + * @spi_clk: Peripheral clock + * @spi_hclk: AHB bus clock + * @cur_transfer: Currently processed SPI transfer + * @xfer_len: Number of bytes to transfer + * @num_xfered: Number of transferred bytes + * @tx_sgl: TX transfer scatterlist + * @rx_sgl: RX transfer scatterlist + * @tx_sgl_len: Size of TX DMA transfer + * @rx_sgl_len: Size of RX DMA transfer + * @dev_comp: Device data structure + * @spi_clk_hz: Current SPI clock in Hz + * @spimem_done: SPI-MEM operation completion + * @use_spimem: Enables SPI-MEM + * @dev: Device pointer + * @tx_dma: DMA start for SPI-MEM TX + * @rx_dma: DMA start for SPI-MEM RX + */ struct mtk_spi { void __iomem *base; u32 state; int pad_num; u32 *pad_sel; - struct clk *parent_clk, *sel_clk, *spi_clk; + struct clk *parent_clk, *sel_clk, *spi_clk, *spi_hclk; struct spi_transfer *cur_transfer; u32 xfer_len; u32 num_xfered; @@ -123,6 +166,11 @@ struct mtk_spi { u32 tx_sgl_len, rx_sgl_len; const struct mtk_spi_compatible *dev_comp; u32 spi_clk_hz; + struct completion spimem_done; + bool use_spimem; + struct device *dev; + dma_addr_t tx_dma; + dma_addr_t rx_dma; }; static const struct mtk_spi_compatible mtk_common_compat; @@ -704,6 +752,12 @@ static irqreturn_t mtk_spi_interrupt(int irq, void *dev_id) else mdata->state = MTK_SPI_IDLE; + /* SPI-MEM ops */ + if (mdata->use_spimem) { + complete(&mdata->spimem_done); + return IRQ_HANDLED; + } + if (!master->can_dma(master, NULL, trans)) { if (trans->rx_buf) { cnt = mdata->xfer_len / 4; @@ -787,21 +841,287 @@ static irqreturn_t mtk_spi_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } -static int mtk_spi_probe(struct platform_device *pdev) +static int mtk_spi_mem_adjust_op_size(struct spi_mem *mem, + struct spi_mem_op *op) { - struct spi_master *master; - struct mtk_spi *mdata; - const struct of_device_id *of_id; - int i, irq, ret, addr_bits; + int opcode_len; - master = spi_alloc_master(&pdev->dev, sizeof(*mdata)); - if (!master) { - dev_err(&pdev->dev, "failed to alloc spi master\n"); + if (op->data.dir != SPI_MEM_NO_DATA) { + opcode_len = 1 + op->addr.nbytes + op->dummy.nbytes; + if (opcode_len + op->data.nbytes > MTK_SPI_IPM_PACKET_SIZE) { + op->data.nbytes = MTK_SPI_IPM_PACKET_SIZE - opcode_len; + /* force data buffer dma-aligned. */ + op->data.nbytes -= op->data.nbytes % 4; + } + } + + return 0; +} + +static bool mtk_spi_mem_supports_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + if (!spi_mem_default_supports_op(mem, op)) + return false; + + if (op->addr.nbytes && op->dummy.nbytes && + op->addr.buswidth != op->dummy.buswidth) + return false; + + if (op->addr.nbytes + op->dummy.nbytes > 16) + return false; + + if (op->data.nbytes > MTK_SPI_IPM_PACKET_SIZE) { + if (op->data.nbytes / MTK_SPI_IPM_PACKET_SIZE > + MTK_SPI_IPM_PACKET_LOOP || + op->data.nbytes % MTK_SPI_IPM_PACKET_SIZE != 0) + return false; + } + + return true; +} + +static void mtk_spi_mem_setup_dma_xfer(struct spi_master *master, + const struct spi_mem_op *op) +{ + struct mtk_spi *mdata = spi_master_get_devdata(master); + + writel((u32)(mdata->tx_dma & MTK_SPI_32BITS_MASK), + mdata->base + SPI_TX_SRC_REG); +#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT + if (mdata->dev_comp->dma_ext) + writel((u32)(mdata->tx_dma >> 32), + mdata->base + SPI_TX_SRC_REG_64); +#endif + + if (op->data.dir == SPI_MEM_DATA_IN) { + writel((u32)(mdata->rx_dma & MTK_SPI_32BITS_MASK), + mdata->base + SPI_RX_DST_REG); +#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT + if (mdata->dev_comp->dma_ext) + writel((u32)(mdata->rx_dma >> 32), + mdata->base + SPI_RX_DST_REG_64); +#endif + } +} + +static int mtk_spi_transfer_wait(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + struct mtk_spi *mdata = spi_master_get_devdata(mem->spi->master); + /* + * For each byte we wait for 8 cycles of the SPI clock. + * Since speed is defined in Hz and we want milliseconds, + * so it should be 8 * 1000. + */ + u64 ms = 8000LL; + + if (op->data.dir == SPI_MEM_NO_DATA) + ms *= 32; /* prevent we may get 0 for short transfers. */ + else + ms *= op->data.nbytes; + ms = div_u64(ms, mem->spi->max_speed_hz); + ms += ms + 1000; /* 1s tolerance */ + + if (ms > UINT_MAX) + ms = UINT_MAX; + + if (!wait_for_completion_timeout(&mdata->spimem_done, + msecs_to_jiffies(ms))) { + dev_err(mdata->dev, "spi-mem transfer timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int mtk_spi_mem_exec_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + struct mtk_spi *mdata = spi_master_get_devdata(mem->spi->master); + u32 reg_val, nio, tx_size; + char *tx_tmp_buf, *rx_tmp_buf; + int ret = 0; + + mdata->use_spimem = true; + reinit_completion(&mdata->spimem_done); + + mtk_spi_reset(mdata); + mtk_spi_hw_init(mem->spi->master, mem->spi); + mtk_spi_prepare_transfer(mem->spi->master, mem->spi->max_speed_hz); + + reg_val = readl(mdata->base + SPI_CFG3_IPM_REG); + /* opcode byte len */ + reg_val &= ~SPI_CFG3_IPM_CMD_BYTELEN_MASK; + reg_val |= 1 << SPI_CFG3_IPM_CMD_BYTELEN_OFFSET; + + /* addr & dummy byte len */ + reg_val &= ~SPI_CFG3_IPM_ADDR_BYTELEN_MASK; + if (op->addr.nbytes || op->dummy.nbytes) + reg_val |= (op->addr.nbytes + op->dummy.nbytes) << + SPI_CFG3_IPM_ADDR_BYTELEN_OFFSET; + + /* data byte len */ + if (op->data.dir == SPI_MEM_NO_DATA) { + reg_val |= SPI_CFG3_IPM_NODATA_FLAG; + writel(0, mdata->base + SPI_CFG1_REG); + } else { + reg_val &= ~SPI_CFG3_IPM_NODATA_FLAG; + mdata->xfer_len = op->data.nbytes; + mtk_spi_setup_packet(mem->spi->master); + } + + if (op->addr.nbytes || op->dummy.nbytes) { + if (op->addr.buswidth == 1 || op->dummy.buswidth == 1) + reg_val |= SPI_CFG3_IPM_XMODE_EN; + else + reg_val &= ~SPI_CFG3_IPM_XMODE_EN; + } + + if (op->addr.buswidth == 2 || + op->dummy.buswidth == 2 || + op->data.buswidth == 2) + nio = 2; + else if (op->addr.buswidth == 4 || + op->dummy.buswidth == 4 || + op->data.buswidth == 4) + nio = 4; + else + nio = 1; + + reg_val &= ~SPI_CFG3_IPM_CMD_PIN_MODE_MASK; + reg_val |= PIN_MODE_CFG(nio); + + reg_val |= SPI_CFG3_IPM_HALF_DUPLEX_EN; + if (op->data.dir == SPI_MEM_DATA_IN) + reg_val |= SPI_CFG3_IPM_HALF_DUPLEX_DIR; + else + reg_val &= ~SPI_CFG3_IPM_HALF_DUPLEX_DIR; + writel(reg_val, mdata->base + SPI_CFG3_IPM_REG); + + tx_size = 1 + op->addr.nbytes + op->dummy.nbytes; + if (op->data.dir == SPI_MEM_DATA_OUT) + tx_size += op->data.nbytes; + + tx_size = max_t(u32, tx_size, 32); + + tx_tmp_buf = kzalloc(tx_size, GFP_KERNEL | GFP_DMA); + if (!tx_tmp_buf) { + mdata->use_spimem = false; return -ENOMEM; } + tx_tmp_buf[0] = op->cmd.opcode; + + if (op->addr.nbytes) { + int i; + + for (i = 0; i < op->addr.nbytes; i++) + tx_tmp_buf[i + 1] = op->addr.val >> + (8 * (op->addr.nbytes - i - 1)); + } + + if (op->dummy.nbytes) + memset(tx_tmp_buf + op->addr.nbytes + 1, + 0xff, + op->dummy.nbytes); + + if (op->data.nbytes && op->data.dir == SPI_MEM_DATA_OUT) + memcpy(tx_tmp_buf + op->dummy.nbytes + op->addr.nbytes + 1, + op->data.buf.out, + op->data.nbytes); + + mdata->tx_dma = dma_map_single(mdata->dev, tx_tmp_buf, + tx_size, DMA_TO_DEVICE); + if (dma_mapping_error(mdata->dev, mdata->tx_dma)) { + ret = -ENOMEM; + goto err_exit; + } + + if (op->data.dir == SPI_MEM_DATA_IN) { + if (!IS_ALIGNED((size_t)op->data.buf.in, 4)) { + rx_tmp_buf = kzalloc(op->data.nbytes, + GFP_KERNEL | GFP_DMA); + if (!rx_tmp_buf) { + ret = -ENOMEM; + goto unmap_tx_dma; + } + } else { + rx_tmp_buf = op->data.buf.in; + } + + mdata->rx_dma = dma_map_single(mdata->dev, + rx_tmp_buf, + op->data.nbytes, + DMA_FROM_DEVICE); + if (dma_mapping_error(mdata->dev, mdata->rx_dma)) { + ret = -ENOMEM; + goto kfree_rx_tmp_buf; + } + } + + reg_val = readl(mdata->base + SPI_CMD_REG); + reg_val |= SPI_CMD_TX_DMA; + if (op->data.dir == SPI_MEM_DATA_IN) + reg_val |= SPI_CMD_RX_DMA; + writel(reg_val, mdata->base + SPI_CMD_REG); + + mtk_spi_mem_setup_dma_xfer(mem->spi->master, op); + + mtk_spi_enable_transfer(mem->spi->master); + + /* Wait for the interrupt. */ + ret = mtk_spi_transfer_wait(mem, op); + if (ret) + goto unmap_rx_dma; + + /* spi disable dma */ + reg_val = readl(mdata->base + SPI_CMD_REG); + reg_val &= ~SPI_CMD_TX_DMA; + if (op->data.dir == SPI_MEM_DATA_IN) + reg_val &= ~SPI_CMD_RX_DMA; + writel(reg_val, mdata->base + SPI_CMD_REG); + +unmap_rx_dma: + if (op->data.dir == SPI_MEM_DATA_IN) { + dma_unmap_single(mdata->dev, mdata->rx_dma, + op->data.nbytes, DMA_FROM_DEVICE); + if (!IS_ALIGNED((size_t)op->data.buf.in, 4)) + memcpy(op->data.buf.in, rx_tmp_buf, op->data.nbytes); + } +kfree_rx_tmp_buf: + if (op->data.dir == SPI_MEM_DATA_IN && + !IS_ALIGNED((size_t)op->data.buf.in, 4)) + kfree(rx_tmp_buf); +unmap_tx_dma: + dma_unmap_single(mdata->dev, mdata->tx_dma, + tx_size, DMA_TO_DEVICE); +err_exit: + kfree(tx_tmp_buf); + mdata->use_spimem = false; + + return ret; +} + +static const struct spi_controller_mem_ops mtk_spi_mem_ops = { + .adjust_op_size = mtk_spi_mem_adjust_op_size, + .supports_op = mtk_spi_mem_supports_op, + .exec_op = mtk_spi_mem_exec_op, +}; + +static int mtk_spi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spi_master *master; + struct mtk_spi *mdata; + int i, irq, ret, addr_bits; + + master = devm_spi_alloc_master(dev, sizeof(*mdata)); + if (!master) + return dev_err_probe(dev, -ENOMEM, "failed to alloc spi master\n"); + master->auto_runtime_pm = true; - master->dev.of_node = pdev->dev.of_node; + master->dev.of_node = dev->of_node; master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST; master->set_cs = mtk_spi_set_cs; @@ -812,15 +1132,8 @@ static int mtk_spi_probe(struct platform_device *pdev) master->set_cs_timing = mtk_spi_set_hw_cs_timing; master->use_gpio_descriptors = true; - of_id = of_match_node(mtk_spi_of_match, pdev->dev.of_node); - if (!of_id) { - dev_err(&pdev->dev, "failed to probe of_node\n"); - ret = -EINVAL; - goto err_put_master; - } - mdata = spi_master_get_devdata(master); - mdata->dev_comp = of_id->data; + mdata->dev_comp = device_get_match_data(dev); if (mdata->dev_comp->enhance_timing) master->mode_bits |= SPI_CS_HIGH; @@ -830,143 +1143,122 @@ static int mtk_spi_probe(struct platform_device *pdev) if (mdata->dev_comp->ipm_design) master->mode_bits |= SPI_LOOP; - if (mdata->dev_comp->need_pad_sel) { - mdata->pad_num = of_property_count_u32_elems( - pdev->dev.of_node, - "mediatek,pad-select"); - if (mdata->pad_num < 0) { - dev_err(&pdev->dev, - "No 'mediatek,pad-select' property\n"); - ret = -EINVAL; - goto err_put_master; - } + if (mdata->dev_comp->ipm_design) { + mdata->dev = dev; + master->mem_ops = &mtk_spi_mem_ops; + init_completion(&mdata->spimem_done); + } - mdata->pad_sel = devm_kmalloc_array(&pdev->dev, mdata->pad_num, + if (mdata->dev_comp->need_pad_sel) { + mdata->pad_num = of_property_count_u32_elems(dev->of_node, + "mediatek,pad-select"); + if (mdata->pad_num < 0) + return dev_err_probe(dev, -EINVAL, + "No 'mediatek,pad-select' property\n"); + + mdata->pad_sel = devm_kmalloc_array(dev, mdata->pad_num, sizeof(u32), GFP_KERNEL); - if (!mdata->pad_sel) { - ret = -ENOMEM; - goto err_put_master; - } + if (!mdata->pad_sel) + return -ENOMEM; for (i = 0; i < mdata->pad_num; i++) { - of_property_read_u32_index(pdev->dev.of_node, + of_property_read_u32_index(dev->of_node, "mediatek,pad-select", i, &mdata->pad_sel[i]); - if (mdata->pad_sel[i] > MT8173_SPI_MAX_PAD_SEL) { - dev_err(&pdev->dev, "wrong pad-sel[%d]: %u\n", - i, mdata->pad_sel[i]); - ret = -EINVAL; - goto err_put_master; - } + if (mdata->pad_sel[i] > MT8173_SPI_MAX_PAD_SEL) + return dev_err_probe(dev, -EINVAL, + "wrong pad-sel[%d]: %u\n", + i, mdata->pad_sel[i]); } } platform_set_drvdata(pdev, master); mdata->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(mdata->base)) { - ret = PTR_ERR(mdata->base); - goto err_put_master; - } + if (IS_ERR(mdata->base)) + return PTR_ERR(mdata->base); irq = platform_get_irq(pdev, 0); - if (irq < 0) { - ret = irq; - goto err_put_master; - } + if (irq < 0) + return irq; - if (!pdev->dev.dma_mask) - pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + if (!dev->dma_mask) + dev->dma_mask = &dev->coherent_dma_mask; - ret = devm_request_irq(&pdev->dev, irq, mtk_spi_interrupt, - IRQF_TRIGGER_NONE, dev_name(&pdev->dev), master); - if (ret) { - dev_err(&pdev->dev, "failed to register irq (%d)\n", ret); - goto err_put_master; - } + ret = devm_request_irq(dev, irq, mtk_spi_interrupt, + IRQF_TRIGGER_NONE, dev_name(dev), master); + if (ret) + return dev_err_probe(dev, ret, "failed to register irq\n"); - mdata->parent_clk = devm_clk_get(&pdev->dev, "parent-clk"); - if (IS_ERR(mdata->parent_clk)) { - ret = PTR_ERR(mdata->parent_clk); - dev_err(&pdev->dev, "failed to get parent-clk: %d\n", ret); - goto err_put_master; - } + mdata->parent_clk = devm_clk_get(dev, "parent-clk"); + if (IS_ERR(mdata->parent_clk)) + return dev_err_probe(dev, PTR_ERR(mdata->parent_clk), + "failed to get parent-clk\n"); - mdata->sel_clk = devm_clk_get(&pdev->dev, "sel-clk"); - if (IS_ERR(mdata->sel_clk)) { - ret = PTR_ERR(mdata->sel_clk); - dev_err(&pdev->dev, "failed to get sel-clk: %d\n", ret); - goto err_put_master; - } + mdata->sel_clk = devm_clk_get(dev, "sel-clk"); + if (IS_ERR(mdata->sel_clk)) + return dev_err_probe(dev, PTR_ERR(mdata->sel_clk), "failed to get sel-clk\n"); - mdata->spi_clk = devm_clk_get(&pdev->dev, "spi-clk"); - if (IS_ERR(mdata->spi_clk)) { - ret = PTR_ERR(mdata->spi_clk); - dev_err(&pdev->dev, "failed to get spi-clk: %d\n", ret); - goto err_put_master; - } + mdata->spi_clk = devm_clk_get(dev, "spi-clk"); + if (IS_ERR(mdata->spi_clk)) + return dev_err_probe(dev, PTR_ERR(mdata->spi_clk), "failed to get spi-clk\n"); + + mdata->spi_hclk = devm_clk_get_optional(dev, "hclk"); + if (IS_ERR(mdata->spi_hclk)) + return dev_err_probe(dev, PTR_ERR(mdata->spi_hclk), "failed to get hclk\n"); + + ret = clk_set_parent(mdata->sel_clk, mdata->parent_clk); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to clk_set_parent\n"); + + ret = clk_prepare_enable(mdata->spi_hclk); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to enable hclk\n"); ret = clk_prepare_enable(mdata->spi_clk); if (ret < 0) { - dev_err(&pdev->dev, "failed to enable spi_clk (%d)\n", ret); - goto err_put_master; - } - - ret = clk_set_parent(mdata->sel_clk, mdata->parent_clk); - if (ret < 0) { - dev_err(&pdev->dev, "failed to clk_set_parent (%d)\n", ret); - clk_disable_unprepare(mdata->spi_clk); - goto err_put_master; + clk_disable_unprepare(mdata->spi_hclk); + return dev_err_probe(dev, ret, "failed to enable spi_clk\n"); } mdata->spi_clk_hz = clk_get_rate(mdata->spi_clk); - if (mdata->dev_comp->no_need_unprepare) + if (mdata->dev_comp->no_need_unprepare) { clk_disable(mdata->spi_clk); - else + clk_disable(mdata->spi_hclk); + } else { clk_disable_unprepare(mdata->spi_clk); - - pm_runtime_enable(&pdev->dev); + clk_disable_unprepare(mdata->spi_hclk); + } if (mdata->dev_comp->need_pad_sel) { - if (mdata->pad_num != master->num_chipselect) { - dev_err(&pdev->dev, + if (mdata->pad_num != master->num_chipselect) + return dev_err_probe(dev, -EINVAL, "pad_num does not match num_chipselect(%d != %d)\n", mdata->pad_num, master->num_chipselect); - ret = -EINVAL; - goto err_disable_runtime_pm; - } - if (!master->cs_gpiods && master->num_chipselect > 1) { - dev_err(&pdev->dev, + if (!master->cs_gpiods && master->num_chipselect > 1) + return dev_err_probe(dev, -EINVAL, "cs_gpios not specified and num_chipselect > 1\n"); - ret = -EINVAL; - goto err_disable_runtime_pm; - } } if (mdata->dev_comp->dma_ext) addr_bits = DMA_ADDR_EXT_BITS; else addr_bits = DMA_ADDR_DEF_BITS; - ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(addr_bits)); + ret = dma_set_mask(dev, DMA_BIT_MASK(addr_bits)); if (ret) - dev_notice(&pdev->dev, "SPI dma_set_mask(%d) failed, ret:%d\n", + dev_notice(dev, "SPI dma_set_mask(%d) failed, ret:%d\n", addr_bits, ret); - ret = devm_spi_register_master(&pdev->dev, master); + pm_runtime_enable(dev); + + ret = devm_spi_register_master(dev, master); if (ret) { - dev_err(&pdev->dev, "failed to register master (%d)\n", ret); - goto err_disable_runtime_pm; + pm_runtime_disable(dev); + return dev_err_probe(dev, ret, "failed to register master\n"); } return 0; - -err_disable_runtime_pm: - pm_runtime_disable(&pdev->dev); -err_put_master: - spi_master_put(master); - - return ret; } static int mtk_spi_remove(struct platform_device *pdev) @@ -978,8 +1270,10 @@ static int mtk_spi_remove(struct platform_device *pdev) mtk_spi_reset(mdata); - if (mdata->dev_comp->no_need_unprepare) + if (mdata->dev_comp->no_need_unprepare) { clk_unprepare(mdata->spi_clk); + clk_unprepare(mdata->spi_hclk); + } return 0; } @@ -995,8 +1289,10 @@ static int mtk_spi_suspend(struct device *dev) if (ret) return ret; - if (!pm_runtime_suspended(dev)) + if (!pm_runtime_suspended(dev)) { clk_disable_unprepare(mdata->spi_clk); + clk_disable_unprepare(mdata->spi_hclk); + } return ret; } @@ -1013,11 +1309,20 @@ static int mtk_spi_resume(struct device *dev) dev_err(dev, "failed to enable spi_clk (%d)\n", ret); return ret; } + + ret = clk_prepare_enable(mdata->spi_hclk); + if (ret < 0) { + dev_err(dev, "failed to enable spi_hclk (%d)\n", ret); + clk_disable_unprepare(mdata->spi_clk); + return ret; + } } ret = spi_master_resume(master); - if (ret < 0) + if (ret < 0) { clk_disable_unprepare(mdata->spi_clk); + clk_disable_unprepare(mdata->spi_hclk); + } return ret; } @@ -1029,10 +1334,13 @@ static int mtk_spi_runtime_suspend(struct device *dev) struct spi_master *master = dev_get_drvdata(dev); struct mtk_spi *mdata = spi_master_get_devdata(master); - if (mdata->dev_comp->no_need_unprepare) + if (mdata->dev_comp->no_need_unprepare) { clk_disable(mdata->spi_clk); - else + clk_disable(mdata->spi_hclk); + } else { clk_disable_unprepare(mdata->spi_clk); + clk_disable_unprepare(mdata->spi_hclk); + } return 0; } @@ -1043,13 +1351,31 @@ static int mtk_spi_runtime_resume(struct device *dev) struct mtk_spi *mdata = spi_master_get_devdata(master); int ret; - if (mdata->dev_comp->no_need_unprepare) + if (mdata->dev_comp->no_need_unprepare) { ret = clk_enable(mdata->spi_clk); - else + if (ret < 0) { + dev_err(dev, "failed to enable spi_clk (%d)\n", ret); + return ret; + } + ret = clk_enable(mdata->spi_hclk); + if (ret < 0) { + dev_err(dev, "failed to enable spi_hclk (%d)\n", ret); + clk_disable(mdata->spi_clk); + return ret; + } + } else { ret = clk_prepare_enable(mdata->spi_clk); - if (ret < 0) { - dev_err(dev, "failed to enable spi_clk (%d)\n", ret); - return ret; + if (ret < 0) { + dev_err(dev, "failed to prepare_enable spi_clk (%d)\n", ret); + return ret; + } + + ret = clk_prepare_enable(mdata->spi_hclk); + if (ret < 0) { + dev_err(dev, "failed to prepare_enable spi_hclk (%d)\n", ret); + clk_disable_unprepare(mdata->spi_clk); + return ret; + } } return 0; diff --git a/drivers/spi/spi-mtk-snfi.c b/drivers/spi/spi-mtk-snfi.c new file mode 100644 index 000000000000..d66bf9762557 --- /dev/null +++ b/drivers/spi/spi-mtk-snfi.c @@ -0,0 +1,1472 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Driver for the SPI-NAND mode of Mediatek NAND Flash Interface +// +// Copyright (c) 2022 Chuanhong Guo +// +// This driver is based on the SPI-NAND mtd driver from Mediatek SDK: +// +// Copyright (C) 2020 MediaTek Inc. +// Author: Weijie Gao +// +// This controller organize the page data as several interleaved sectors +// like the following: (sizeof(FDM + ECC) = snf->nfi_cfg.spare_size) +// +---------+------+------+---------+------+------+-----+ +// | Sector1 | FDM1 | ECC1 | Sector2 | FDM2 | ECC2 | ... | +// +---------+------+------+---------+------+------+-----+ +// With auto-format turned on, DMA only returns this part: +// +---------+---------+-----+ +// | Sector1 | Sector2 | ... | +// +---------+---------+-----+ +// The FDM data will be filled to the registers, and ECC parity data isn't +// accessible. +// With auto-format off, all ((Sector+FDM+ECC)*nsectors) will be read over DMA +// in it's original order shown in the first table. ECC can't be turned on when +// auto-format is off. +// +// However, Linux SPI-NAND driver expects the data returned as: +// +------+-----+ +// | Page | OOB | +// +------+-----+ +// where the page data is continuously stored instead of interleaved. +// So we assume all instructions matching the page_op template between ECC +// prepare_io_req and finish_io_req are for page cache r/w. +// Here's how this spi-mem driver operates when reading: +// 1. Always set snf->autofmt = true in prepare_io_req (even when ECC is off). +// 2. Perform page ops and let the controller fill the DMA bounce buffer with +// de-interleaved sector data and set FDM registers. +// 3. Return the data as: +// +---------+---------+-----+------+------+-----+ +// | Sector1 | Sector2 | ... | FDM1 | FDM2 | ... | +// +---------+---------+-----+------+------+-----+ +// 4. For other matching spi_mem ops outside a prepare/finish_io_req pair, +// read the data with auto-format off into the bounce buffer and copy +// needed data to the buffer specified in the request. +// +// Write requests operates in a similar manner. +// As a limitation of this strategy, we won't be able to access any ECC parity +// data at all in Linux. +// +// Here's the bad block mark situation on MTK chips: +// In older chips like mt7622, MTK uses the first FDM byte in the first sector +// as the bad block mark. After de-interleaving, this byte appears at [pagesize] +// in the returned data, which is the BBM position expected by kernel. However, +// the conventional bad block mark is the first byte of the OOB, which is part +// of the last sector data in the interleaved layout. Instead of fixing their +// hardware, MTK decided to address this inconsistency in software. On these +// later chips, the BootROM expects the following: +// 1. The [pagesize] byte on a nand page is used as BBM, which will appear at +// (page_size - (nsectors - 1) * spare_size) in the DMA buffer. +// 2. The original byte stored at that position in the DMA buffer will be stored +// as the first byte of the FDM section in the last sector. +// We can't disagree with the BootROM, so after de-interleaving, we need to +// perform the following swaps in read: +// 1. Store the BBM at [page_size - (nsectors - 1) * spare_size] to [page_size], +// which is the expected BBM position by kernel. +// 2. Store the page data byte at [pagesize + (nsectors-1) * fdm] back to +// [page_size - (nsectors - 1) * spare_size] +// Similarly, when writing, we need to perform swaps in the other direction. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// NFI registers +#define NFI_CNFG 0x000 +#define CNFG_OP_MODE_S 12 +#define CNFG_OP_MODE_CUST 6 +#define CNFG_OP_MODE_PROGRAM 3 +#define CNFG_AUTO_FMT_EN BIT(9) +#define CNFG_HW_ECC_EN BIT(8) +#define CNFG_DMA_BURST_EN BIT(2) +#define CNFG_READ_MODE BIT(1) +#define CNFG_DMA_MODE BIT(0) + +#define NFI_PAGEFMT 0x0004 +#define NFI_SPARE_SIZE_LS_S 16 +#define NFI_FDM_ECC_NUM_S 12 +#define NFI_FDM_NUM_S 8 +#define NFI_SPARE_SIZE_S 4 +#define NFI_SEC_SEL_512 BIT(2) +#define NFI_PAGE_SIZE_S 0 +#define NFI_PAGE_SIZE_512_2K 0 +#define NFI_PAGE_SIZE_2K_4K 1 +#define NFI_PAGE_SIZE_4K_8K 2 +#define NFI_PAGE_SIZE_8K_16K 3 + +#define NFI_CON 0x008 +#define CON_SEC_NUM_S 12 +#define CON_BWR BIT(9) +#define CON_BRD BIT(8) +#define CON_NFI_RST BIT(1) +#define CON_FIFO_FLUSH BIT(0) + +#define NFI_INTR_EN 0x010 +#define NFI_INTR_STA 0x014 +#define NFI_IRQ_INTR_EN BIT(31) +#define NFI_IRQ_CUS_READ BIT(8) +#define NFI_IRQ_CUS_PG BIT(7) + +#define NFI_CMD 0x020 +#define NFI_CMD_DUMMY_READ 0x00 +#define NFI_CMD_DUMMY_WRITE 0x80 + +#define NFI_STRDATA 0x040 +#define STR_DATA BIT(0) + +#define NFI_STA 0x060 +#define NFI_NAND_FSM GENMASK(28, 24) +#define NFI_FSM GENMASK(19, 16) +#define READ_EMPTY BIT(12) + +#define NFI_FIFOSTA 0x064 +#define FIFO_WR_REMAIN_S 8 +#define FIFO_RD_REMAIN_S 0 + +#define NFI_ADDRCNTR 0x070 +#define SEC_CNTR GENMASK(16, 12) +#define SEC_CNTR_S 12 +#define NFI_SEC_CNTR(val) (((val)&SEC_CNTR) >> SEC_CNTR_S) + +#define NFI_STRADDR 0x080 + +#define NFI_BYTELEN 0x084 +#define BUS_SEC_CNTR(val) (((val)&SEC_CNTR) >> SEC_CNTR_S) + +#define NFI_FDM0L 0x0a0 +#define NFI_FDM0M 0x0a4 +#define NFI_FDML(n) (NFI_FDM0L + (n)*8) +#define NFI_FDMM(n) (NFI_FDM0M + (n)*8) + +#define NFI_DEBUG_CON1 0x220 +#define WBUF_EN BIT(2) + +#define NFI_MASTERSTA 0x224 +#define MAS_ADDR GENMASK(11, 9) +#define MAS_RD GENMASK(8, 6) +#define MAS_WR GENMASK(5, 3) +#define MAS_RDDLY GENMASK(2, 0) +#define NFI_MASTERSTA_MASK_7622 (MAS_ADDR | MAS_RD | MAS_WR | MAS_RDDLY) + +// SNFI registers +#define SNF_MAC_CTL 0x500 +#define MAC_XIO_SEL BIT(4) +#define SF_MAC_EN BIT(3) +#define SF_TRIG BIT(2) +#define WIP_READY BIT(1) +#define WIP BIT(0) + +#define SNF_MAC_OUTL 0x504 +#define SNF_MAC_INL 0x508 + +#define SNF_RD_CTL2 0x510 +#define DATA_READ_DUMMY_S 8 +#define DATA_READ_MAX_DUMMY 0xf +#define DATA_READ_CMD_S 0 + +#define SNF_RD_CTL3 0x514 + +#define SNF_PG_CTL1 0x524 +#define PG_LOAD_CMD_S 8 + +#define SNF_PG_CTL2 0x528 + +#define SNF_MISC_CTL 0x538 +#define SW_RST BIT(28) +#define FIFO_RD_LTC_S 25 +#define PG_LOAD_X4_EN BIT(20) +#define DATA_READ_MODE_S 16 +#define DATA_READ_MODE GENMASK(18, 16) +#define DATA_READ_MODE_X1 0 +#define DATA_READ_MODE_X2 1 +#define DATA_READ_MODE_X4 2 +#define DATA_READ_MODE_DUAL 5 +#define DATA_READ_MODE_QUAD 6 +#define PG_LOAD_CUSTOM_EN BIT(7) +#define DATARD_CUSTOM_EN BIT(6) +#define CS_DESELECT_CYC_S 0 + +#define SNF_MISC_CTL2 0x53c +#define PROGRAM_LOAD_BYTE_NUM_S 16 +#define READ_DATA_BYTE_NUM_S 11 + +#define SNF_DLY_CTL3 0x548 +#define SFCK_SAM_DLY_S 0 + +#define SNF_STA_CTL1 0x550 +#define CUS_PG_DONE BIT(28) +#define CUS_READ_DONE BIT(27) +#define SPI_STATE_S 0 +#define SPI_STATE GENMASK(3, 0) + +#define SNF_CFG 0x55c +#define SPI_MODE BIT(0) + +#define SNF_GPRAM 0x800 +#define SNF_GPRAM_SIZE 0xa0 + +#define SNFI_POLL_INTERVAL 1000000 + +static const u8 mt7622_spare_sizes[] = { 16, 26, 27, 28 }; + +struct mtk_snand_caps { + u16 sector_size; + u16 max_sectors; + u16 fdm_size; + u16 fdm_ecc_size; + u16 fifo_size; + + bool bbm_swap; + bool empty_page_check; + u32 mastersta_mask; + + const u8 *spare_sizes; + u32 num_spare_size; +}; + +static const struct mtk_snand_caps mt7622_snand_caps = { + .sector_size = 512, + .max_sectors = 8, + .fdm_size = 8, + .fdm_ecc_size = 1, + .fifo_size = 32, + .bbm_swap = false, + .empty_page_check = false, + .mastersta_mask = NFI_MASTERSTA_MASK_7622, + .spare_sizes = mt7622_spare_sizes, + .num_spare_size = ARRAY_SIZE(mt7622_spare_sizes) +}; + +static const struct mtk_snand_caps mt7629_snand_caps = { + .sector_size = 512, + .max_sectors = 8, + .fdm_size = 8, + .fdm_ecc_size = 1, + .fifo_size = 32, + .bbm_swap = true, + .empty_page_check = false, + .mastersta_mask = NFI_MASTERSTA_MASK_7622, + .spare_sizes = mt7622_spare_sizes, + .num_spare_size = ARRAY_SIZE(mt7622_spare_sizes) +}; + +struct mtk_snand_conf { + size_t page_size; + size_t oob_size; + u8 nsectors; + u8 spare_size; +}; + +struct mtk_snand { + struct spi_controller *ctlr; + struct device *dev; + struct clk *nfi_clk; + struct clk *pad_clk; + void __iomem *nfi_base; + int irq; + struct completion op_done; + const struct mtk_snand_caps *caps; + struct mtk_ecc_config *ecc_cfg; + struct mtk_ecc *ecc; + struct mtk_snand_conf nfi_cfg; + struct mtk_ecc_stats ecc_stats; + struct nand_ecc_engine ecc_eng; + bool autofmt; + u8 *buf; + size_t buf_len; +}; + +static struct mtk_snand *nand_to_mtk_snand(struct nand_device *nand) +{ + struct nand_ecc_engine *eng = nand->ecc.engine; + + return container_of(eng, struct mtk_snand, ecc_eng); +} + +static inline int snand_prepare_bouncebuf(struct mtk_snand *snf, size_t size) +{ + if (snf->buf_len >= size) + return 0; + kfree(snf->buf); + snf->buf = kmalloc(size, GFP_KERNEL); + if (!snf->buf) + return -ENOMEM; + snf->buf_len = size; + memset(snf->buf, 0xff, snf->buf_len); + return 0; +} + +static inline u32 nfi_read32(struct mtk_snand *snf, u32 reg) +{ + return readl(snf->nfi_base + reg); +} + +static inline void nfi_write32(struct mtk_snand *snf, u32 reg, u32 val) +{ + writel(val, snf->nfi_base + reg); +} + +static inline void nfi_write16(struct mtk_snand *snf, u32 reg, u16 val) +{ + writew(val, snf->nfi_base + reg); +} + +static inline void nfi_rmw32(struct mtk_snand *snf, u32 reg, u32 clr, u32 set) +{ + u32 val; + + val = readl(snf->nfi_base + reg); + val &= ~clr; + val |= set; + writel(val, snf->nfi_base + reg); +} + +static void nfi_read_data(struct mtk_snand *snf, u32 reg, u8 *data, u32 len) +{ + u32 i, val = 0, es = sizeof(u32); + + for (i = reg; i < reg + len; i++) { + if (i == reg || i % es == 0) + val = nfi_read32(snf, i & ~(es - 1)); + + *data++ = (u8)(val >> (8 * (i % es))); + } +} + +static int mtk_nfi_reset(struct mtk_snand *snf) +{ + u32 val, fifo_mask; + int ret; + + nfi_write32(snf, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST); + + ret = readw_poll_timeout(snf->nfi_base + NFI_MASTERSTA, val, + !(val & snf->caps->mastersta_mask), 0, + SNFI_POLL_INTERVAL); + if (ret) { + dev_err(snf->dev, "NFI master is still busy after reset\n"); + return ret; + } + + ret = readl_poll_timeout(snf->nfi_base + NFI_STA, val, + !(val & (NFI_FSM | NFI_NAND_FSM)), 0, + SNFI_POLL_INTERVAL); + if (ret) { + dev_err(snf->dev, "Failed to reset NFI\n"); + return ret; + } + + fifo_mask = ((snf->caps->fifo_size - 1) << FIFO_RD_REMAIN_S) | + ((snf->caps->fifo_size - 1) << FIFO_WR_REMAIN_S); + ret = readw_poll_timeout(snf->nfi_base + NFI_FIFOSTA, val, + !(val & fifo_mask), 0, SNFI_POLL_INTERVAL); + if (ret) { + dev_err(snf->dev, "NFI FIFOs are not empty\n"); + return ret; + } + + return 0; +} + +static int mtk_snand_mac_reset(struct mtk_snand *snf) +{ + int ret; + u32 val; + + nfi_rmw32(snf, SNF_MISC_CTL, 0, SW_RST); + + ret = readl_poll_timeout(snf->nfi_base + SNF_STA_CTL1, val, + !(val & SPI_STATE), 0, SNFI_POLL_INTERVAL); + if (ret) + dev_err(snf->dev, "Failed to reset SNFI MAC\n"); + + nfi_write32(snf, SNF_MISC_CTL, + (2 << FIFO_RD_LTC_S) | (10 << CS_DESELECT_CYC_S)); + + return ret; +} + +static int mtk_snand_mac_trigger(struct mtk_snand *snf, u32 outlen, u32 inlen) +{ + int ret; + u32 val; + + nfi_write32(snf, SNF_MAC_CTL, SF_MAC_EN); + nfi_write32(snf, SNF_MAC_OUTL, outlen); + nfi_write32(snf, SNF_MAC_INL, inlen); + + nfi_write32(snf, SNF_MAC_CTL, SF_MAC_EN | SF_TRIG); + + ret = readl_poll_timeout(snf->nfi_base + SNF_MAC_CTL, val, + val & WIP_READY, 0, SNFI_POLL_INTERVAL); + if (ret) { + dev_err(snf->dev, "Timed out waiting for WIP_READY\n"); + goto cleanup; + } + + ret = readl_poll_timeout(snf->nfi_base + SNF_MAC_CTL, val, !(val & WIP), + 0, SNFI_POLL_INTERVAL); + if (ret) + dev_err(snf->dev, "Timed out waiting for WIP cleared\n"); + +cleanup: + nfi_write32(snf, SNF_MAC_CTL, 0); + + return ret; +} + +static int mtk_snand_mac_io(struct mtk_snand *snf, const struct spi_mem_op *op) +{ + u32 rx_len = 0; + u32 reg_offs = 0; + u32 val = 0; + const u8 *tx_buf = NULL; + u8 *rx_buf = NULL; + int i, ret; + u8 b; + + if (op->data.dir == SPI_MEM_DATA_IN) { + rx_len = op->data.nbytes; + rx_buf = op->data.buf.in; + } else { + tx_buf = op->data.buf.out; + } + + mtk_snand_mac_reset(snf); + + for (i = 0; i < op->cmd.nbytes; i++, reg_offs++) { + b = (op->cmd.opcode >> ((op->cmd.nbytes - i - 1) * 8)) & 0xff; + val |= b << (8 * (reg_offs % 4)); + if (reg_offs % 4 == 3) { + nfi_write32(snf, SNF_GPRAM + reg_offs - 3, val); + val = 0; + } + } + + for (i = 0; i < op->addr.nbytes; i++, reg_offs++) { + b = (op->addr.val >> ((op->addr.nbytes - i - 1) * 8)) & 0xff; + val |= b << (8 * (reg_offs % 4)); + if (reg_offs % 4 == 3) { + nfi_write32(snf, SNF_GPRAM + reg_offs - 3, val); + val = 0; + } + } + + for (i = 0; i < op->dummy.nbytes; i++, reg_offs++) { + if (reg_offs % 4 == 3) { + nfi_write32(snf, SNF_GPRAM + reg_offs - 3, val); + val = 0; + } + } + + if (op->data.dir == SPI_MEM_DATA_OUT) { + for (i = 0; i < op->data.nbytes; i++, reg_offs++) { + val |= tx_buf[i] << (8 * (reg_offs % 4)); + if (reg_offs % 4 == 3) { + nfi_write32(snf, SNF_GPRAM + reg_offs - 3, val); + val = 0; + } + } + } + + if (reg_offs % 4) + nfi_write32(snf, SNF_GPRAM + (reg_offs & ~3), val); + + for (i = 0; i < reg_offs; i += 4) + dev_dbg(snf->dev, "%d: %08X", i, + nfi_read32(snf, SNF_GPRAM + i)); + + dev_dbg(snf->dev, "SNF TX: %u RX: %u", reg_offs, rx_len); + + ret = mtk_snand_mac_trigger(snf, reg_offs, rx_len); + if (ret) + return ret; + + if (!rx_len) + return 0; + + nfi_read_data(snf, SNF_GPRAM + reg_offs, rx_buf, rx_len); + return 0; +} + +static int mtk_snand_setup_pagefmt(struct mtk_snand *snf, u32 page_size, + u32 oob_size) +{ + int spare_idx = -1; + u32 spare_size, spare_size_shift, pagesize_idx; + u32 sector_size_512; + u8 nsectors; + int i; + + // skip if it's already configured as required. + if (snf->nfi_cfg.page_size == page_size && + snf->nfi_cfg.oob_size == oob_size) + return 0; + + nsectors = page_size / snf->caps->sector_size; + if (nsectors > snf->caps->max_sectors) { + dev_err(snf->dev, "too many sectors required.\n"); + goto err; + } + + if (snf->caps->sector_size == 512) { + sector_size_512 = NFI_SEC_SEL_512; + spare_size_shift = NFI_SPARE_SIZE_S; + } else { + sector_size_512 = 0; + spare_size_shift = NFI_SPARE_SIZE_LS_S; + } + + switch (page_size) { + case SZ_512: + pagesize_idx = NFI_PAGE_SIZE_512_2K; + break; + case SZ_2K: + if (snf->caps->sector_size == 512) + pagesize_idx = NFI_PAGE_SIZE_2K_4K; + else + pagesize_idx = NFI_PAGE_SIZE_512_2K; + break; + case SZ_4K: + if (snf->caps->sector_size == 512) + pagesize_idx = NFI_PAGE_SIZE_4K_8K; + else + pagesize_idx = NFI_PAGE_SIZE_2K_4K; + break; + case SZ_8K: + if (snf->caps->sector_size == 512) + pagesize_idx = NFI_PAGE_SIZE_8K_16K; + else + pagesize_idx = NFI_PAGE_SIZE_4K_8K; + break; + case SZ_16K: + pagesize_idx = NFI_PAGE_SIZE_8K_16K; + break; + default: + dev_err(snf->dev, "unsupported page size.\n"); + goto err; + } + + spare_size = oob_size / nsectors; + // If we're using the 1KB sector size, HW will automatically double the + // spare size. We should only use half of the value in this case. + if (snf->caps->sector_size == 1024) + spare_size /= 2; + + for (i = snf->caps->num_spare_size - 1; i >= 0; i--) { + if (snf->caps->spare_sizes[i] <= spare_size) { + spare_size = snf->caps->spare_sizes[i]; + if (snf->caps->sector_size == 1024) + spare_size *= 2; + spare_idx = i; + break; + } + } + + if (spare_idx < 0) { + dev_err(snf->dev, "unsupported spare size: %u\n", spare_size); + goto err; + } + + nfi_write32(snf, NFI_PAGEFMT, + (snf->caps->fdm_ecc_size << NFI_FDM_ECC_NUM_S) | + (snf->caps->fdm_size << NFI_FDM_NUM_S) | + (spare_idx << spare_size_shift) | + (pagesize_idx << NFI_PAGE_SIZE_S) | + sector_size_512); + + snf->nfi_cfg.page_size = page_size; + snf->nfi_cfg.oob_size = oob_size; + snf->nfi_cfg.nsectors = nsectors; + snf->nfi_cfg.spare_size = spare_size; + + dev_dbg(snf->dev, "page format: (%u + %u) * %u\n", + snf->caps->sector_size, spare_size, nsectors); + return snand_prepare_bouncebuf(snf, page_size + oob_size); +err: + dev_err(snf->dev, "page size %u + %u is not supported\n", page_size, + oob_size); + return -EOPNOTSUPP; +} + +static int mtk_snand_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobecc) +{ + // ECC area is not accessible + return -ERANGE; +} + +static int mtk_snand_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobfree) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + struct mtk_snand *ms = nand_to_mtk_snand(nand); + + if (section >= ms->nfi_cfg.nsectors) + return -ERANGE; + + oobfree->length = ms->caps->fdm_size - 1; + oobfree->offset = section * ms->caps->fdm_size + 1; + return 0; +} + +static const struct mtd_ooblayout_ops mtk_snand_ooblayout = { + .ecc = mtk_snand_ooblayout_ecc, + .free = mtk_snand_ooblayout_free, +}; + +static int mtk_snand_ecc_init_ctx(struct nand_device *nand) +{ + struct mtk_snand *snf = nand_to_mtk_snand(nand); + struct nand_ecc_props *conf = &nand->ecc.ctx.conf; + struct nand_ecc_props *reqs = &nand->ecc.requirements; + struct nand_ecc_props *user = &nand->ecc.user_conf; + struct mtd_info *mtd = nanddev_to_mtd(nand); + int step_size = 0, strength = 0, desired_correction = 0, steps; + bool ecc_user = false; + int ret; + u32 parity_bits, max_ecc_bytes; + struct mtk_ecc_config *ecc_cfg; + + ret = mtk_snand_setup_pagefmt(snf, nand->memorg.pagesize, + nand->memorg.oobsize); + if (ret) + return ret; + + ecc_cfg = kzalloc(sizeof(*ecc_cfg), GFP_KERNEL); + if (!ecc_cfg) + return -ENOMEM; + + nand->ecc.ctx.priv = ecc_cfg; + + if (user->step_size && user->strength) { + step_size = user->step_size; + strength = user->strength; + ecc_user = true; + } else if (reqs->step_size && reqs->strength) { + step_size = reqs->step_size; + strength = reqs->strength; + } + + if (step_size && strength) { + steps = mtd->writesize / step_size; + desired_correction = steps * strength; + strength = desired_correction / snf->nfi_cfg.nsectors; + } + + ecc_cfg->mode = ECC_NFI_MODE; + ecc_cfg->sectors = snf->nfi_cfg.nsectors; + ecc_cfg->len = snf->caps->sector_size + snf->caps->fdm_ecc_size; + + // calculate the max possible strength under current page format + parity_bits = mtk_ecc_get_parity_bits(snf->ecc); + max_ecc_bytes = snf->nfi_cfg.spare_size - snf->caps->fdm_size; + ecc_cfg->strength = max_ecc_bytes * 8 / parity_bits; + mtk_ecc_adjust_strength(snf->ecc, &ecc_cfg->strength); + + // if there's a user requested strength, find the minimum strength that + // meets the requirement. Otherwise use the maximum strength which is + // expected by BootROM. + if (ecc_user && strength) { + u32 s_next = ecc_cfg->strength - 1; + + while (1) { + mtk_ecc_adjust_strength(snf->ecc, &s_next); + if (s_next >= ecc_cfg->strength) + break; + if (s_next < strength) + break; + s_next = ecc_cfg->strength - 1; + } + } + + mtd_set_ooblayout(mtd, &mtk_snand_ooblayout); + + conf->step_size = snf->caps->sector_size; + conf->strength = ecc_cfg->strength; + + if (ecc_cfg->strength < strength) + dev_warn(snf->dev, "unable to fulfill ECC of %u bits.\n", + strength); + dev_info(snf->dev, "ECC strength: %u bits per %u bytes\n", + ecc_cfg->strength, snf->caps->sector_size); + + return 0; +} + +static void mtk_snand_ecc_cleanup_ctx(struct nand_device *nand) +{ + struct mtk_ecc_config *ecc_cfg = nand_to_ecc_ctx(nand); + + kfree(ecc_cfg); +} + +static int mtk_snand_ecc_prepare_io_req(struct nand_device *nand, + struct nand_page_io_req *req) +{ + struct mtk_snand *snf = nand_to_mtk_snand(nand); + struct mtk_ecc_config *ecc_cfg = nand_to_ecc_ctx(nand); + int ret; + + ret = mtk_snand_setup_pagefmt(snf, nand->memorg.pagesize, + nand->memorg.oobsize); + if (ret) + return ret; + snf->autofmt = true; + snf->ecc_cfg = ecc_cfg; + return 0; +} + +static int mtk_snand_ecc_finish_io_req(struct nand_device *nand, + struct nand_page_io_req *req) +{ + struct mtk_snand *snf = nand_to_mtk_snand(nand); + struct mtd_info *mtd = nanddev_to_mtd(nand); + + snf->ecc_cfg = NULL; + snf->autofmt = false; + if ((req->mode == MTD_OPS_RAW) || (req->type != NAND_PAGE_READ)) + return 0; + + if (snf->ecc_stats.failed) + mtd->ecc_stats.failed += snf->ecc_stats.failed; + mtd->ecc_stats.corrected += snf->ecc_stats.corrected; + return snf->ecc_stats.failed ? -EBADMSG : snf->ecc_stats.bitflips; +} + +static struct nand_ecc_engine_ops mtk_snfi_ecc_engine_ops = { + .init_ctx = mtk_snand_ecc_init_ctx, + .cleanup_ctx = mtk_snand_ecc_cleanup_ctx, + .prepare_io_req = mtk_snand_ecc_prepare_io_req, + .finish_io_req = mtk_snand_ecc_finish_io_req, +}; + +static void mtk_snand_read_fdm(struct mtk_snand *snf, u8 *buf) +{ + u32 vall, valm; + u8 *oobptr = buf; + int i, j; + + for (i = 0; i < snf->nfi_cfg.nsectors; i++) { + vall = nfi_read32(snf, NFI_FDML(i)); + valm = nfi_read32(snf, NFI_FDMM(i)); + + for (j = 0; j < snf->caps->fdm_size; j++) + oobptr[j] = (j >= 4 ? valm : vall) >> ((j % 4) * 8); + + oobptr += snf->caps->fdm_size; + } +} + +static void mtk_snand_write_fdm(struct mtk_snand *snf, const u8 *buf) +{ + u32 fdm_size = snf->caps->fdm_size; + const u8 *oobptr = buf; + u32 vall, valm; + int i, j; + + for (i = 0; i < snf->nfi_cfg.nsectors; i++) { + vall = 0; + valm = 0; + + for (j = 0; j < 8; j++) { + if (j < 4) + vall |= (j < fdm_size ? oobptr[j] : 0xff) + << (j * 8); + else + valm |= (j < fdm_size ? oobptr[j] : 0xff) + << ((j - 4) * 8); + } + + nfi_write32(snf, NFI_FDML(i), vall); + nfi_write32(snf, NFI_FDMM(i), valm); + + oobptr += fdm_size; + } +} + +static void mtk_snand_bm_swap(struct mtk_snand *snf, u8 *buf) +{ + u32 buf_bbm_pos, fdm_bbm_pos; + + if (!snf->caps->bbm_swap || snf->nfi_cfg.nsectors == 1) + return; + + // swap [pagesize] byte on nand with the first fdm byte + // in the last sector. + buf_bbm_pos = snf->nfi_cfg.page_size - + (snf->nfi_cfg.nsectors - 1) * snf->nfi_cfg.spare_size; + fdm_bbm_pos = snf->nfi_cfg.page_size + + (snf->nfi_cfg.nsectors - 1) * snf->caps->fdm_size; + + swap(snf->buf[fdm_bbm_pos], buf[buf_bbm_pos]); +} + +static void mtk_snand_fdm_bm_swap(struct mtk_snand *snf) +{ + u32 fdm_bbm_pos1, fdm_bbm_pos2; + + if (!snf->caps->bbm_swap || snf->nfi_cfg.nsectors == 1) + return; + + // swap the first fdm byte in the first and the last sector. + fdm_bbm_pos1 = snf->nfi_cfg.page_size; + fdm_bbm_pos2 = snf->nfi_cfg.page_size + + (snf->nfi_cfg.nsectors - 1) * snf->caps->fdm_size; + swap(snf->buf[fdm_bbm_pos1], snf->buf[fdm_bbm_pos2]); +} + +static int mtk_snand_read_page_cache(struct mtk_snand *snf, + const struct spi_mem_op *op) +{ + u8 *buf = snf->buf; + u8 *buf_fdm = buf + snf->nfi_cfg.page_size; + // the address part to be sent by the controller + u32 op_addr = op->addr.val; + // where to start copying data from bounce buffer + u32 rd_offset = 0; + u32 dummy_clk = (op->dummy.nbytes * BITS_PER_BYTE / op->dummy.buswidth); + u32 op_mode = 0; + u32 dma_len = snf->buf_len; + int ret = 0; + u32 rd_mode, rd_bytes, val; + dma_addr_t buf_dma; + + if (snf->autofmt) { + u32 last_bit; + u32 mask; + + dma_len = snf->nfi_cfg.page_size; + op_mode = CNFG_AUTO_FMT_EN; + if (op->data.ecc) + op_mode |= CNFG_HW_ECC_EN; + // extract the plane bit: + // Find the highest bit set in (pagesize+oobsize). + // Bits higher than that in op->addr are kept and sent over SPI + // Lower bits are used as an offset for copying data from DMA + // bounce buffer. + last_bit = fls(snf->nfi_cfg.page_size + snf->nfi_cfg.oob_size); + mask = (1 << last_bit) - 1; + rd_offset = op_addr & mask; + op_addr &= ~mask; + + // check if we can dma to the caller memory + if (rd_offset == 0 && op->data.nbytes >= snf->nfi_cfg.page_size) + buf = op->data.buf.in; + } + mtk_snand_mac_reset(snf); + mtk_nfi_reset(snf); + + // command and dummy cycles + nfi_write32(snf, SNF_RD_CTL2, + (dummy_clk << DATA_READ_DUMMY_S) | + (op->cmd.opcode << DATA_READ_CMD_S)); + + // read address + nfi_write32(snf, SNF_RD_CTL3, op_addr); + + // Set read op_mode + if (op->data.buswidth == 4) + rd_mode = op->addr.buswidth == 4 ? DATA_READ_MODE_QUAD : + DATA_READ_MODE_X4; + else if (op->data.buswidth == 2) + rd_mode = op->addr.buswidth == 2 ? DATA_READ_MODE_DUAL : + DATA_READ_MODE_X2; + else + rd_mode = DATA_READ_MODE_X1; + rd_mode <<= DATA_READ_MODE_S; + nfi_rmw32(snf, SNF_MISC_CTL, DATA_READ_MODE, + rd_mode | DATARD_CUSTOM_EN); + + // Set bytes to read + rd_bytes = (snf->nfi_cfg.spare_size + snf->caps->sector_size) * + snf->nfi_cfg.nsectors; + nfi_write32(snf, SNF_MISC_CTL2, + (rd_bytes << PROGRAM_LOAD_BYTE_NUM_S) | rd_bytes); + + // NFI read prepare + nfi_write16(snf, NFI_CNFG, + (CNFG_OP_MODE_CUST << CNFG_OP_MODE_S) | CNFG_DMA_BURST_EN | + CNFG_READ_MODE | CNFG_DMA_MODE | op_mode); + + nfi_write32(snf, NFI_CON, (snf->nfi_cfg.nsectors << CON_SEC_NUM_S)); + + buf_dma = dma_map_single(snf->dev, buf, dma_len, DMA_FROM_DEVICE); + ret = dma_mapping_error(snf->dev, buf_dma); + if (ret) { + dev_err(snf->dev, "DMA mapping failed.\n"); + goto cleanup; + } + nfi_write32(snf, NFI_STRADDR, buf_dma); + if (op->data.ecc) { + snf->ecc_cfg->op = ECC_DECODE; + ret = mtk_ecc_enable(snf->ecc, snf->ecc_cfg); + if (ret) + goto cleanup_dma; + } + // Prepare for custom read interrupt + nfi_write32(snf, NFI_INTR_EN, NFI_IRQ_INTR_EN | NFI_IRQ_CUS_READ); + reinit_completion(&snf->op_done); + + // Trigger NFI into custom mode + nfi_write16(snf, NFI_CMD, NFI_CMD_DUMMY_READ); + + // Start DMA read + nfi_rmw32(snf, NFI_CON, 0, CON_BRD); + nfi_write16(snf, NFI_STRDATA, STR_DATA); + + if (!wait_for_completion_timeout( + &snf->op_done, usecs_to_jiffies(SNFI_POLL_INTERVAL))) { + dev_err(snf->dev, "DMA timed out for reading from cache.\n"); + ret = -ETIMEDOUT; + goto cleanup; + } + + // Wait for BUS_SEC_CNTR returning expected value + ret = readl_poll_timeout(snf->nfi_base + NFI_BYTELEN, val, + BUS_SEC_CNTR(val) >= snf->nfi_cfg.nsectors, 0, + SNFI_POLL_INTERVAL); + if (ret) { + dev_err(snf->dev, "Timed out waiting for BUS_SEC_CNTR\n"); + goto cleanup2; + } + + // Wait for bus becoming idle + ret = readl_poll_timeout(snf->nfi_base + NFI_MASTERSTA, val, + !(val & snf->caps->mastersta_mask), 0, + SNFI_POLL_INTERVAL); + if (ret) { + dev_err(snf->dev, "Timed out waiting for bus becoming idle\n"); + goto cleanup2; + } + + if (op->data.ecc) { + ret = mtk_ecc_wait_done(snf->ecc, ECC_DECODE); + if (ret) { + dev_err(snf->dev, "wait ecc done timeout\n"); + goto cleanup2; + } + // save status before disabling ecc + mtk_ecc_get_stats(snf->ecc, &snf->ecc_stats, + snf->nfi_cfg.nsectors); + } + + dma_unmap_single(snf->dev, buf_dma, dma_len, DMA_FROM_DEVICE); + + if (snf->autofmt) { + mtk_snand_read_fdm(snf, buf_fdm); + if (snf->caps->bbm_swap) { + mtk_snand_bm_swap(snf, buf); + mtk_snand_fdm_bm_swap(snf); + } + } + + // copy data back + if (nfi_read32(snf, NFI_STA) & READ_EMPTY) { + memset(op->data.buf.in, 0xff, op->data.nbytes); + snf->ecc_stats.bitflips = 0; + snf->ecc_stats.failed = 0; + snf->ecc_stats.corrected = 0; + } else { + if (buf == op->data.buf.in) { + u32 cap_len = snf->buf_len - snf->nfi_cfg.page_size; + u32 req_left = op->data.nbytes - snf->nfi_cfg.page_size; + + if (req_left) + memcpy(op->data.buf.in + snf->nfi_cfg.page_size, + buf_fdm, + cap_len < req_left ? cap_len : req_left); + } else if (rd_offset < snf->buf_len) { + u32 cap_len = snf->buf_len - rd_offset; + + if (op->data.nbytes < cap_len) + cap_len = op->data.nbytes; + memcpy(op->data.buf.in, snf->buf + rd_offset, cap_len); + } + } +cleanup2: + if (op->data.ecc) + mtk_ecc_disable(snf->ecc); +cleanup_dma: + // unmap dma only if any error happens. (otherwise it's done before + // data copying) + if (ret) + dma_unmap_single(snf->dev, buf_dma, dma_len, DMA_FROM_DEVICE); +cleanup: + // Stop read + nfi_write32(snf, NFI_CON, 0); + nfi_write16(snf, NFI_CNFG, 0); + + // Clear SNF done flag + nfi_rmw32(snf, SNF_STA_CTL1, 0, CUS_READ_DONE); + nfi_write32(snf, SNF_STA_CTL1, 0); + + // Disable interrupt + nfi_read32(snf, NFI_INTR_STA); + nfi_write32(snf, NFI_INTR_EN, 0); + + nfi_rmw32(snf, SNF_MISC_CTL, DATARD_CUSTOM_EN, 0); + return ret; +} + +static int mtk_snand_write_page_cache(struct mtk_snand *snf, + const struct spi_mem_op *op) +{ + // the address part to be sent by the controller + u32 op_addr = op->addr.val; + // where to start copying data from bounce buffer + u32 wr_offset = 0; + u32 op_mode = 0; + int ret = 0; + u32 wr_mode = 0; + u32 dma_len = snf->buf_len; + u32 wr_bytes, val; + size_t cap_len; + dma_addr_t buf_dma; + + if (snf->autofmt) { + u32 last_bit; + u32 mask; + + dma_len = snf->nfi_cfg.page_size; + op_mode = CNFG_AUTO_FMT_EN; + if (op->data.ecc) + op_mode |= CNFG_HW_ECC_EN; + + last_bit = fls(snf->nfi_cfg.page_size + snf->nfi_cfg.oob_size); + mask = (1 << last_bit) - 1; + wr_offset = op_addr & mask; + op_addr &= ~mask; + } + mtk_snand_mac_reset(snf); + mtk_nfi_reset(snf); + + if (wr_offset) + memset(snf->buf, 0xff, wr_offset); + + cap_len = snf->buf_len - wr_offset; + if (op->data.nbytes < cap_len) + cap_len = op->data.nbytes; + memcpy(snf->buf + wr_offset, op->data.buf.out, cap_len); + if (snf->autofmt) { + if (snf->caps->bbm_swap) { + mtk_snand_fdm_bm_swap(snf); + mtk_snand_bm_swap(snf, snf->buf); + } + mtk_snand_write_fdm(snf, snf->buf + snf->nfi_cfg.page_size); + } + + // Command + nfi_write32(snf, SNF_PG_CTL1, (op->cmd.opcode << PG_LOAD_CMD_S)); + + // write address + nfi_write32(snf, SNF_PG_CTL2, op_addr); + + // Set read op_mode + if (op->data.buswidth == 4) + wr_mode = PG_LOAD_X4_EN; + + nfi_rmw32(snf, SNF_MISC_CTL, PG_LOAD_X4_EN, + wr_mode | PG_LOAD_CUSTOM_EN); + + // Set bytes to write + wr_bytes = (snf->nfi_cfg.spare_size + snf->caps->sector_size) * + snf->nfi_cfg.nsectors; + nfi_write32(snf, SNF_MISC_CTL2, + (wr_bytes << PROGRAM_LOAD_BYTE_NUM_S) | wr_bytes); + + // NFI write prepare + nfi_write16(snf, NFI_CNFG, + (CNFG_OP_MODE_PROGRAM << CNFG_OP_MODE_S) | + CNFG_DMA_BURST_EN | CNFG_DMA_MODE | op_mode); + + nfi_write32(snf, NFI_CON, (snf->nfi_cfg.nsectors << CON_SEC_NUM_S)); + buf_dma = dma_map_single(snf->dev, snf->buf, dma_len, DMA_TO_DEVICE); + ret = dma_mapping_error(snf->dev, buf_dma); + if (ret) { + dev_err(snf->dev, "DMA mapping failed.\n"); + goto cleanup; + } + nfi_write32(snf, NFI_STRADDR, buf_dma); + if (op->data.ecc) { + snf->ecc_cfg->op = ECC_ENCODE; + ret = mtk_ecc_enable(snf->ecc, snf->ecc_cfg); + if (ret) + goto cleanup_dma; + } + // Prepare for custom write interrupt + nfi_write32(snf, NFI_INTR_EN, NFI_IRQ_INTR_EN | NFI_IRQ_CUS_PG); + reinit_completion(&snf->op_done); + ; + + // Trigger NFI into custom mode + nfi_write16(snf, NFI_CMD, NFI_CMD_DUMMY_WRITE); + + // Start DMA write + nfi_rmw32(snf, NFI_CON, 0, CON_BWR); + nfi_write16(snf, NFI_STRDATA, STR_DATA); + + if (!wait_for_completion_timeout( + &snf->op_done, usecs_to_jiffies(SNFI_POLL_INTERVAL))) { + dev_err(snf->dev, "DMA timed out for program load.\n"); + ret = -ETIMEDOUT; + goto cleanup_ecc; + } + + // Wait for NFI_SEC_CNTR returning expected value + ret = readl_poll_timeout(snf->nfi_base + NFI_ADDRCNTR, val, + NFI_SEC_CNTR(val) >= snf->nfi_cfg.nsectors, 0, + SNFI_POLL_INTERVAL); + if (ret) + dev_err(snf->dev, "Timed out waiting for NFI_SEC_CNTR\n"); + +cleanup_ecc: + if (op->data.ecc) + mtk_ecc_disable(snf->ecc); +cleanup_dma: + dma_unmap_single(snf->dev, buf_dma, dma_len, DMA_TO_DEVICE); +cleanup: + // Stop write + nfi_write32(snf, NFI_CON, 0); + nfi_write16(snf, NFI_CNFG, 0); + + // Clear SNF done flag + nfi_rmw32(snf, SNF_STA_CTL1, 0, CUS_PG_DONE); + nfi_write32(snf, SNF_STA_CTL1, 0); + + // Disable interrupt + nfi_read32(snf, NFI_INTR_STA); + nfi_write32(snf, NFI_INTR_EN, 0); + + nfi_rmw32(snf, SNF_MISC_CTL, PG_LOAD_CUSTOM_EN, 0); + + return ret; +} + +/** + * mtk_snand_is_page_ops() - check if the op is a controller supported page op. + * @op spi-mem op to check + * + * Check whether op can be executed with read_from_cache or program_load + * mode in the controller. + * This controller can execute typical Read From Cache and Program Load + * instructions found on SPI-NAND with 2-byte address. + * DTR and cmd buswidth & nbytes should be checked before calling this. + * + * Return: true if the op matches the instruction template + */ +static bool mtk_snand_is_page_ops(const struct spi_mem_op *op) +{ + if (op->addr.nbytes != 2) + return false; + + if (op->addr.buswidth != 1 && op->addr.buswidth != 2 && + op->addr.buswidth != 4) + return false; + + // match read from page instructions + if (op->data.dir == SPI_MEM_DATA_IN) { + // check dummy cycle first + if (op->dummy.nbytes * BITS_PER_BYTE / op->dummy.buswidth > + DATA_READ_MAX_DUMMY) + return false; + // quad io / quad out + if ((op->addr.buswidth == 4 || op->addr.buswidth == 1) && + op->data.buswidth == 4) + return true; + + // dual io / dual out + if ((op->addr.buswidth == 2 || op->addr.buswidth == 1) && + op->data.buswidth == 2) + return true; + + // standard spi + if (op->addr.buswidth == 1 && op->data.buswidth == 1) + return true; + } else if (op->data.dir == SPI_MEM_DATA_OUT) { + // check dummy cycle first + if (op->dummy.nbytes) + return false; + // program load quad out + if (op->addr.buswidth == 1 && op->data.buswidth == 4) + return true; + // standard spi + if (op->addr.buswidth == 1 && op->data.buswidth == 1) + return true; + } + return false; +} + +static bool mtk_snand_supports_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + if (!spi_mem_default_supports_op(mem, op)) + return false; + if (op->cmd.nbytes != 1 || op->cmd.buswidth != 1) + return false; + if (mtk_snand_is_page_ops(op)) + return true; + return ((op->addr.nbytes == 0 || op->addr.buswidth == 1) && + (op->dummy.nbytes == 0 || op->dummy.buswidth == 1) && + (op->data.nbytes == 0 || op->data.buswidth == 1)); +} + +static int mtk_snand_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op) +{ + struct mtk_snand *ms = spi_controller_get_devdata(mem->spi->master); + // page ops transfer size must be exactly ((sector_size + spare_size) * + // nsectors). Limit the op size if the caller requests more than that. + // exec_op will read more than needed and discard the leftover if the + // caller requests less data. + if (mtk_snand_is_page_ops(op)) { + size_t l; + // skip adjust_op_size for page ops + if (ms->autofmt) + return 0; + l = ms->caps->sector_size + ms->nfi_cfg.spare_size; + l *= ms->nfi_cfg.nsectors; + if (op->data.nbytes > l) + op->data.nbytes = l; + } else { + size_t hl = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes; + + if (hl >= SNF_GPRAM_SIZE) + return -EOPNOTSUPP; + if (op->data.nbytes > SNF_GPRAM_SIZE - hl) + op->data.nbytes = SNF_GPRAM_SIZE - hl; + } + return 0; +} + +static int mtk_snand_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) +{ + struct mtk_snand *ms = spi_controller_get_devdata(mem->spi->master); + + dev_dbg(ms->dev, "OP %02x ADDR %08llX@%d:%u DATA %d:%u", op->cmd.opcode, + op->addr.val, op->addr.buswidth, op->addr.nbytes, + op->data.buswidth, op->data.nbytes); + if (mtk_snand_is_page_ops(op)) { + if (op->data.dir == SPI_MEM_DATA_IN) + return mtk_snand_read_page_cache(ms, op); + else + return mtk_snand_write_page_cache(ms, op); + } else { + return mtk_snand_mac_io(ms, op); + } +} + +static const struct spi_controller_mem_ops mtk_snand_mem_ops = { + .adjust_op_size = mtk_snand_adjust_op_size, + .supports_op = mtk_snand_supports_op, + .exec_op = mtk_snand_exec_op, +}; + +static const struct spi_controller_mem_caps mtk_snand_mem_caps = { + .ecc = true, +}; + +static irqreturn_t mtk_snand_irq(int irq, void *id) +{ + struct mtk_snand *snf = id; + u32 sta, ien; + + sta = nfi_read32(snf, NFI_INTR_STA); + ien = nfi_read32(snf, NFI_INTR_EN); + + if (!(sta & ien)) + return IRQ_NONE; + + nfi_write32(snf, NFI_INTR_EN, 0); + complete(&snf->op_done); + return IRQ_HANDLED; +} + +static const struct of_device_id mtk_snand_ids[] = { + { .compatible = "mediatek,mt7622-snand", .data = &mt7622_snand_caps }, + { .compatible = "mediatek,mt7629-snand", .data = &mt7629_snand_caps }, + {}, +}; + +MODULE_DEVICE_TABLE(of, mtk_snand_ids); + +static int mtk_snand_enable_clk(struct mtk_snand *ms) +{ + int ret; + + ret = clk_prepare_enable(ms->nfi_clk); + if (ret) { + dev_err(ms->dev, "unable to enable nfi clk\n"); + return ret; + } + ret = clk_prepare_enable(ms->pad_clk); + if (ret) { + dev_err(ms->dev, "unable to enable pad clk\n"); + goto err1; + } + return 0; +err1: + clk_disable_unprepare(ms->nfi_clk); + return ret; +} + +static void mtk_snand_disable_clk(struct mtk_snand *ms) +{ + clk_disable_unprepare(ms->pad_clk); + clk_disable_unprepare(ms->nfi_clk); +} + +static int mtk_snand_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *dev_id; + struct spi_controller *ctlr; + struct mtk_snand *ms; + int ret; + + dev_id = of_match_node(mtk_snand_ids, np); + if (!dev_id) + return -EINVAL; + + ctlr = devm_spi_alloc_master(&pdev->dev, sizeof(*ms)); + if (!ctlr) + return -ENOMEM; + platform_set_drvdata(pdev, ctlr); + + ms = spi_controller_get_devdata(ctlr); + + ms->ctlr = ctlr; + ms->caps = dev_id->data; + + ms->ecc = of_mtk_ecc_get(np); + if (IS_ERR(ms->ecc)) + return PTR_ERR(ms->ecc); + else if (!ms->ecc) + return -ENODEV; + + ms->nfi_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ms->nfi_base)) { + ret = PTR_ERR(ms->nfi_base); + goto release_ecc; + } + + ms->dev = &pdev->dev; + + ms->nfi_clk = devm_clk_get(&pdev->dev, "nfi_clk"); + if (IS_ERR(ms->nfi_clk)) { + ret = PTR_ERR(ms->nfi_clk); + dev_err(&pdev->dev, "unable to get nfi_clk, err = %d\n", ret); + goto release_ecc; + } + + ms->pad_clk = devm_clk_get(&pdev->dev, "pad_clk"); + if (IS_ERR(ms->pad_clk)) { + ret = PTR_ERR(ms->pad_clk); + dev_err(&pdev->dev, "unable to get pad_clk, err = %d\n", ret); + goto release_ecc; + } + + ret = mtk_snand_enable_clk(ms); + if (ret) + goto release_ecc; + + init_completion(&ms->op_done); + + ms->irq = platform_get_irq(pdev, 0); + if (ms->irq < 0) { + ret = ms->irq; + goto disable_clk; + } + ret = devm_request_irq(ms->dev, ms->irq, mtk_snand_irq, 0x0, + "mtk-snand", ms); + if (ret) { + dev_err(ms->dev, "failed to request snfi irq\n"); + goto disable_clk; + } + + ret = dma_set_mask(ms->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(ms->dev, "failed to set dma mask\n"); + goto disable_clk; + } + + // switch to SNFI mode + nfi_write32(ms, SNF_CFG, SPI_MODE); + + // setup an initial page format for ops matching page_cache_op template + // before ECC is called. + ret = mtk_snand_setup_pagefmt(ms, ms->caps->sector_size, + ms->caps->spare_sizes[0]); + if (ret) { + dev_err(ms->dev, "failed to set initial page format\n"); + goto disable_clk; + } + + // setup ECC engine + ms->ecc_eng.dev = &pdev->dev; + ms->ecc_eng.integration = NAND_ECC_ENGINE_INTEGRATION_PIPELINED; + ms->ecc_eng.ops = &mtk_snfi_ecc_engine_ops; + ms->ecc_eng.priv = ms; + + ret = nand_ecc_register_on_host_hw_engine(&ms->ecc_eng); + if (ret) { + dev_err(&pdev->dev, "failed to register ecc engine.\n"); + goto disable_clk; + } + + ctlr->num_chipselect = 1; + ctlr->mem_ops = &mtk_snand_mem_ops; + ctlr->mem_caps = &mtk_snand_mem_caps; + ctlr->bits_per_word_mask = SPI_BPW_MASK(8); + ctlr->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD; + ctlr->dev.of_node = pdev->dev.of_node; + ret = spi_register_controller(ctlr); + if (ret) { + dev_err(&pdev->dev, "spi_register_controller failed.\n"); + goto disable_clk; + } + + return 0; +disable_clk: + mtk_snand_disable_clk(ms); +release_ecc: + mtk_ecc_release(ms->ecc); + return ret; +} + +static int mtk_snand_remove(struct platform_device *pdev) +{ + struct spi_controller *ctlr = platform_get_drvdata(pdev); + struct mtk_snand *ms = spi_controller_get_devdata(ctlr); + + spi_unregister_controller(ctlr); + mtk_snand_disable_clk(ms); + mtk_ecc_release(ms->ecc); + kfree(ms->buf); + return 0; +} + +static struct platform_driver mtk_snand_driver = { + .probe = mtk_snand_probe, + .remove = mtk_snand_remove, + .driver = { + .name = "mtk-snand", + .of_match_table = mtk_snand_ids, + }, +}; + +module_platform_driver(mtk_snand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Chuanhong Guo "); +MODULE_DESCRIPTION("MeidaTek SPI-NAND Flash Controller Driver"); diff --git a/drivers/spi/spi-mxs.c b/drivers/spi/spi-mxs.c index 435309b09227..55178579f3c6 100644 --- a/drivers/spi/spi-mxs.c +++ b/drivers/spi/spi-mxs.c @@ -605,9 +605,8 @@ static int mxs_spi_probe(struct platform_device *pdev) } } - ret = pm_runtime_get_sync(ssp->dev); + ret = pm_runtime_resume_and_get(ssp->dev); if (ret < 0) { - pm_runtime_put_noidle(ssp->dev); dev_err(ssp->dev, "runtime_get_sync failed\n"); goto out_pm_runtime_disable; } diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c index 60c9cdf1c94b..c42e59df38fe 100644 --- a/drivers/spi/spi-omap2-mcspi.c +++ b/drivers/spi/spi-omap2-mcspi.c @@ -246,9 +246,8 @@ static void omap2_mcspi_set_cs(struct spi_device *spi, bool enable) enable = !enable; if (spi->controller_state) { - int err = pm_runtime_get_sync(mcspi->dev); + int err = pm_runtime_resume_and_get(mcspi->dev); if (err < 0) { - pm_runtime_put_noidle(mcspi->dev); dev_err(mcspi->dev, "failed to get sync: %d\n", err); return; } @@ -758,6 +757,8 @@ omap2_mcspi_txrx_pio(struct spi_device *spi, struct spi_transfer *xfer) dev_vdbg(&spi->dev, "read-%d %02x\n", word_len, *(rx - 1)); } + /* Add word delay between each word */ + spi_delay_exec(&xfer->word_delay, xfer); } while (c); } else if (word_len <= 16) { u16 *rx; @@ -805,6 +806,8 @@ omap2_mcspi_txrx_pio(struct spi_device *spi, struct spi_transfer *xfer) dev_vdbg(&spi->dev, "read-%d %04x\n", word_len, *(rx - 1)); } + /* Add word delay between each word */ + spi_delay_exec(&xfer->word_delay, xfer); } while (c >= 2); } else if (word_len <= 32) { u32 *rx; @@ -852,6 +855,8 @@ omap2_mcspi_txrx_pio(struct spi_device *spi, struct spi_transfer *xfer) dev_vdbg(&spi->dev, "read-%d %08x\n", word_len, *(rx - 1)); } + /* Add word delay between each word */ + spi_delay_exec(&xfer->word_delay, xfer); } while (c >= 4); } @@ -1068,9 +1073,8 @@ static int omap2_mcspi_setup(struct spi_device *spi) initial_setup = true; } - ret = pm_runtime_get_sync(mcspi->dev); + ret = pm_runtime_resume_and_get(mcspi->dev); if (ret < 0) { - pm_runtime_put_noidle(mcspi->dev); if (initial_setup) omap2_mcspi_cleanup(spi); @@ -1317,12 +1321,9 @@ static int omap2_mcspi_controller_setup(struct omap2_mcspi *mcspi) struct omap2_mcspi_regs *ctx = &mcspi->ctx; int ret = 0; - ret = pm_runtime_get_sync(mcspi->dev); - if (ret < 0) { - pm_runtime_put_noidle(mcspi->dev); - + ret = pm_runtime_resume_and_get(mcspi->dev); + if (ret < 0) return ret; - } mcspi_write_reg(master, OMAP2_MCSPI_WAKEUPENABLE, OMAP2_MCSPI_WAKEUPENABLE_WKEN); diff --git a/drivers/spi/spi-rockchip.c b/drivers/spi/spi-rockchip.c index cdc16eecaf6b..a08215eb9e14 100644 --- a/drivers/spi/spi-rockchip.c +++ b/drivers/spi/spi-rockchip.c @@ -196,6 +196,8 @@ struct rockchip_spi { bool slave_abort; bool cs_inactive; /* spi slave tansmition stop when cs inactive */ + bool cs_high_supported; /* native CS supports active-high polarity */ + struct spi_transfer *xfer; /* Store xfer temporarily */ }; @@ -719,6 +721,11 @@ static int rockchip_spi_setup(struct spi_device *spi) struct rockchip_spi *rs = spi_controller_get_devdata(spi->controller); u32 cr0; + if (!spi->cs_gpiod && (spi->mode & SPI_CS_HIGH) && !rs->cs_high_supported) { + dev_warn(&spi->dev, "setup: non GPIO CS can't be active-high\n"); + return -EINVAL; + } + pm_runtime_get_sync(rs->dev); cr0 = readl_relaxed(rs->regs + ROCKCHIP_SPI_CTRLR0); @@ -899,6 +906,7 @@ static int rockchip_spi_probe(struct platform_device *pdev) switch (readl_relaxed(rs->regs + ROCKCHIP_SPI_VERSION)) { case ROCKCHIP_SPI_VER2_TYPE2: + rs->cs_high_supported = true; ctlr->mode_bits |= SPI_CS_HIGH; if (ctlr->can_dma && slave_mode) rs->cs_inactive = true; diff --git a/drivers/spi/spi-rspi.c b/drivers/spi/spi-rspi.c index bd5708d7e5a1..7a014eeec2d0 100644 --- a/drivers/spi/spi-rspi.c +++ b/drivers/spi/spi-rspi.c @@ -1108,14 +1108,11 @@ static struct dma_chan *rspi_request_dma_chan(struct device *dev, } memset(&cfg, 0, sizeof(cfg)); + cfg.dst_addr = port_addr + RSPI_SPDR; + cfg.src_addr = port_addr + RSPI_SPDR; + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; cfg.direction = dir; - if (dir == DMA_MEM_TO_DEV) { - cfg.dst_addr = port_addr; - cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; - } else { - cfg.src_addr = port_addr; - cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; - } ret = dmaengine_slave_config(chan, &cfg); if (ret) { @@ -1146,12 +1143,12 @@ static int rspi_request_dma(struct device *dev, struct spi_controller *ctlr, } ctlr->dma_tx = rspi_request_dma_chan(dev, DMA_MEM_TO_DEV, dma_tx_id, - res->start + RSPI_SPDR); + res->start); if (!ctlr->dma_tx) return -ENODEV; ctlr->dma_rx = rspi_request_dma_chan(dev, DMA_DEV_TO_MEM, dma_rx_id, - res->start + RSPI_SPDR); + res->start); if (!ctlr->dma_rx) { dma_release_channel(ctlr->dma_tx); ctlr->dma_tx = NULL; diff --git a/drivers/spi/spi-sprd.c b/drivers/spi/spi-sprd.c index 28e70db9bbba..65b8075da4eb 100644 --- a/drivers/spi/spi-sprd.c +++ b/drivers/spi/spi-sprd.c @@ -1008,9 +1008,8 @@ static int sprd_spi_remove(struct platform_device *pdev) struct sprd_spi *ss = spi_controller_get_devdata(sctlr); int ret; - ret = pm_runtime_get_sync(ss->dev); + ret = pm_runtime_resume_and_get(ss->dev); if (ret < 0) { - pm_runtime_put_noidle(ss->dev); dev_err(ss->dev, "failed to resume SPI controller\n"); return ret; } diff --git a/drivers/spi/spi-stm32-qspi.c b/drivers/spi/spi-stm32-qspi.c index ffdc55f87e82..c0239e405c39 100644 --- a/drivers/spi/spi-stm32-qspi.c +++ b/drivers/spi/spi-stm32-qspi.c @@ -305,10 +305,8 @@ static int stm32_qspi_wait_cmd(struct stm32_qspi *qspi, u32 cr, sr; int err = 0; - if (!op->data.nbytes) - goto wait_nobusy; - - if (readl_relaxed(qspi->io_base + QSPI_SR) & SR_TCF) + if ((readl_relaxed(qspi->io_base + QSPI_SR) & SR_TCF) || + qspi->fmode == CCR_FMODE_APM) goto out; reinit_completion(&qspi->data_completion); @@ -327,7 +325,6 @@ static int stm32_qspi_wait_cmd(struct stm32_qspi *qspi, out: /* clear flags */ writel_relaxed(FCR_CTCF | FCR_CTEF, qspi->io_base + QSPI_FCR); -wait_nobusy: if (!err) err = stm32_qspi_wait_nobusy(qspi); @@ -372,10 +369,6 @@ static int stm32_qspi_send(struct spi_mem *mem, const struct spi_mem_op *op) op->dummy.buswidth, op->data.buswidth, op->addr.val, op->data.nbytes); - err = stm32_qspi_wait_nobusy(qspi); - if (err) - goto abort; - cr = readl_relaxed(qspi->io_base + QSPI_CR); cr &= ~CR_PRESC_MASK & ~CR_FSEL; cr |= FIELD_PREP(CR_PRESC_MASK, flash->presc); @@ -463,11 +456,9 @@ static int stm32_qspi_poll_status(struct spi_mem *mem, const struct spi_mem_op * if (!spi_mem_supports_op(mem, op)) return -EOPNOTSUPP; - ret = pm_runtime_get_sync(qspi->dev); - if (ret < 0) { - pm_runtime_put_noidle(qspi->dev); + ret = pm_runtime_resume_and_get(qspi->dev); + if (ret < 0) return ret; - } mutex_lock(&qspi->lock); @@ -490,11 +481,9 @@ static int stm32_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) struct stm32_qspi *qspi = spi_controller_get_devdata(mem->spi->master); int ret; - ret = pm_runtime_get_sync(qspi->dev); - if (ret < 0) { - pm_runtime_put_noidle(qspi->dev); + ret = pm_runtime_resume_and_get(qspi->dev); + if (ret < 0) return ret; - } mutex_lock(&qspi->lock); if (op->data.dir == SPI_MEM_DATA_IN && op->data.nbytes) @@ -536,11 +525,9 @@ static ssize_t stm32_qspi_dirmap_read(struct spi_mem_dirmap_desc *desc, u32 addr_max; int ret; - ret = pm_runtime_get_sync(qspi->dev); - if (ret < 0) { - pm_runtime_put_noidle(qspi->dev); + ret = pm_runtime_resume_and_get(qspi->dev); + if (ret < 0) return ret; - } mutex_lock(&qspi->lock); /* make a local copy of desc op_tmpl and complete dirmap rdesc @@ -583,11 +570,9 @@ static int stm32_qspi_setup(struct spi_device *spi) if (!spi->max_speed_hz) return -EINVAL; - ret = pm_runtime_get_sync(qspi->dev); - if (ret < 0) { - pm_runtime_put_noidle(qspi->dev); + ret = pm_runtime_resume_and_get(qspi->dev); + if (ret < 0) return ret; - } presc = DIV_ROUND_UP(qspi->clk_rate, spi->max_speed_hz) - 1; @@ -851,11 +836,9 @@ static int __maybe_unused stm32_qspi_resume(struct device *dev) pinctrl_pm_select_default_state(dev); - ret = pm_runtime_get_sync(dev); - if (ret < 0) { - pm_runtime_put_noidle(dev); + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) return ret; - } writel_relaxed(qspi->cr_reg, qspi->io_base + QSPI_CR); writel_relaxed(qspi->dcr_reg, qspi->io_base + QSPI_DCR); diff --git a/drivers/spi/spi-stm32.c b/drivers/spi/spi-stm32.c index a6adc20f6862..6fe617b445a5 100644 --- a/drivers/spi/spi-stm32.c +++ b/drivers/spi/spi-stm32.c @@ -2000,9 +2000,8 @@ static int __maybe_unused stm32_spi_resume(struct device *dev) return ret; } - ret = pm_runtime_get_sync(dev); + ret = pm_runtime_resume_and_get(dev); if (ret < 0) { - pm_runtime_put_noidle(dev); dev_err(dev, "Unable to power device:%d\n", ret); return ret; } diff --git a/drivers/spi/spi-sunplus-sp7021.c b/drivers/spi/spi-sunplus-sp7021.c index f989f7b99296..f1fa88777575 100644 --- a/drivers/spi/spi-sunplus-sp7021.c +++ b/drivers/spi/spi-sunplus-sp7021.c @@ -85,8 +85,6 @@ struct sp7021_spi_ctlr { int s_irq; struct clk *spi_clk; struct reset_control *rstc; - // irq spin lock - spinlock_t lock; // data xfer lock struct mutex buf_lock; struct completion isr_done; @@ -199,8 +197,6 @@ static irqreturn_t sp7021_spi_master_irq(int irq, void *dev) if (tx_len == 0 && total_len == 0) return IRQ_NONE; - spin_lock_irq(&pspim->lock); - rx_cnt = FIELD_GET(SP7021_RX_CNT_MASK, fd_status); if (fd_status & SP7021_RX_FULL_FLAG) rx_cnt = pspim->data_unit; @@ -239,7 +235,6 @@ static irqreturn_t sp7021_spi_master_irq(int irq, void *dev) if (isrdone) complete(&pspim->isr_done); - spin_unlock_irq(&pspim->lock); return IRQ_HANDLED; } @@ -446,7 +441,6 @@ static int sp7021_spi_controller_probe(struct platform_device *pdev) pspim->mode = mode; pspim->ctlr = ctlr; pspim->dev = dev; - spin_lock_init(&pspim->lock); mutex_init(&pspim->buf_lock); init_completion(&pspim->isr_done); init_completion(&pspim->slave_isr); diff --git a/drivers/spi/spi-tegra114.c b/drivers/spi/spi-tegra114.c index 8f345247a8c3..d9be80e3e1bc 100644 --- a/drivers/spi/spi-tegra114.c +++ b/drivers/spi/spi-tegra114.c @@ -964,9 +964,8 @@ static int tegra_spi_setup(struct spi_device *spi) spi->controller_data = cdata; } - ret = pm_runtime_get_sync(tspi->dev); + ret = pm_runtime_resume_and_get(tspi->dev); if (ret < 0) { - pm_runtime_put_noidle(tspi->dev); dev_err(tspi->dev, "pm runtime failed, e = %d\n", ret); if (cdata) tegra_spi_cleanup(spi); @@ -1394,10 +1393,9 @@ static int tegra_spi_probe(struct platform_device *pdev) goto exit_pm_disable; } - ret = pm_runtime_get_sync(&pdev->dev); + ret = pm_runtime_resume_and_get(&pdev->dev); if (ret < 0) { dev_err(&pdev->dev, "pm runtime get failed, e = %d\n", ret); - pm_runtime_put_noidle(&pdev->dev); goto exit_pm_disable; } @@ -1476,9 +1474,8 @@ static int tegra_spi_resume(struct device *dev) struct tegra_spi_data *tspi = spi_master_get_devdata(master); int ret; - ret = pm_runtime_get_sync(dev); + ret = pm_runtime_resume_and_get(dev); if (ret < 0) { - pm_runtime_put_noidle(dev); dev_err(dev, "pm runtime failed, e = %d\n", ret); return ret; } diff --git a/drivers/spi/spi-tegra20-sflash.c b/drivers/spi/spi-tegra20-sflash.c index 2888d8a8dc6d..220ee08c4a06 100644 --- a/drivers/spi/spi-tegra20-sflash.c +++ b/drivers/spi/spi-tegra20-sflash.c @@ -486,10 +486,9 @@ static int tegra_sflash_probe(struct platform_device *pdev) goto exit_pm_disable; } - ret = pm_runtime_get_sync(&pdev->dev); + ret = pm_runtime_resume_and_get(&pdev->dev); if (ret < 0) { dev_err(&pdev->dev, "pm runtime get failed, e = %d\n", ret); - pm_runtime_put_noidle(&pdev->dev); goto exit_pm_disable; } @@ -549,9 +548,8 @@ static int tegra_sflash_resume(struct device *dev) struct tegra_sflash_data *tsd = spi_master_get_devdata(master); int ret; - ret = pm_runtime_get_sync(dev); + ret = pm_runtime_resume_and_get(dev); if (ret < 0) { - pm_runtime_put_noidle(dev); dev_err(dev, "pm runtime failed, e = %d\n", ret); return ret; } diff --git a/drivers/spi/spi-tegra20-slink.c b/drivers/spi/spi-tegra20-slink.c index 80c3787deea9..38360434d6e9 100644 --- a/drivers/spi/spi-tegra20-slink.c +++ b/drivers/spi/spi-tegra20-slink.c @@ -749,9 +749,8 @@ static int tegra_slink_setup(struct spi_device *spi) spi->mode & SPI_CPHA ? "" : "~", spi->max_speed_hz); - ret = pm_runtime_get_sync(tspi->dev); + ret = pm_runtime_resume_and_get(tspi->dev); if (ret < 0) { - pm_runtime_put_noidle(tspi->dev); dev_err(tspi->dev, "pm runtime failed, e = %d\n", ret); return ret; } @@ -1169,9 +1168,8 @@ static int tegra_slink_resume(struct device *dev) struct tegra_slink_data *tspi = spi_master_get_devdata(master); int ret; - ret = pm_runtime_get_sync(dev); + ret = pm_runtime_resume_and_get(dev); if (ret < 0) { - pm_runtime_put_noidle(dev); dev_err(dev, "pm runtime failed, e = %d\n", ret); return ret; } diff --git a/drivers/spi/spi-ti-qspi.c b/drivers/spi/spi-ti-qspi.c index e06aafe169e0..b5b65d882d7a 100644 --- a/drivers/spi/spi-ti-qspi.c +++ b/drivers/spi/spi-ti-qspi.c @@ -172,9 +172,8 @@ static int ti_qspi_setup(struct spi_device *spi) dev_dbg(qspi->dev, "hz: %d, clock divider %d\n", qspi->spi_max_frequency, clk_div); - ret = pm_runtime_get_sync(qspi->dev); + ret = pm_runtime_resume_and_get(qspi->dev); if (ret < 0) { - pm_runtime_put_noidle(qspi->dev); dev_err(qspi->dev, "pm_runtime_get_sync() failed\n"); return ret; } @@ -448,6 +447,7 @@ static int ti_qspi_dma_xfer(struct ti_qspi *qspi, dma_addr_t dma_dst, enum dma_ctrl_flags flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; struct dma_async_tx_descriptor *tx; int ret; + unsigned long time_left; tx = dmaengine_prep_dma_memcpy(chan, dma_dst, dma_src, len, flags); if (!tx) { @@ -467,9 +467,9 @@ static int ti_qspi_dma_xfer(struct ti_qspi *qspi, dma_addr_t dma_dst, } dma_async_issue_pending(chan); - ret = wait_for_completion_timeout(&qspi->transfer_complete, + time_left = wait_for_completion_timeout(&qspi->transfer_complete, msecs_to_jiffies(len)); - if (ret <= 0) { + if (time_left == 0) { dmaengine_terminate_sync(chan); dev_err(qspi->dev, "DMA wait_for_completion_timeout\n"); return -ETIMEDOUT; diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 2e6d6bbeb784..fe252a8075a7 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1611,9 +1611,8 @@ static void __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread) mutex_lock(&ctlr->io_mutex); if (!was_busy && ctlr->auto_runtime_pm) { - ret = pm_runtime_get_sync(ctlr->dev.parent); + ret = pm_runtime_resume_and_get(ctlr->dev.parent); if (ret < 0) { - pm_runtime_put_noidle(ctlr->dev.parent); dev_err(&ctlr->dev, "Failed to power device: %d\n", ret); mutex_unlock(&ctlr->io_mutex); @@ -3475,7 +3474,7 @@ static int __spi_validate_bits_per_word(struct spi_controller *ctlr, int spi_setup(struct spi_device *spi) { unsigned bad_bits, ugly_bits; - int status; + int status = 0; /* * Check mode to prevent that any two of DUAL, QUAD and NO_MOSI/MISO @@ -3518,13 +3517,18 @@ int spi_setup(struct spi_device *spi) return -EINVAL; } - if (!spi->bits_per_word) + if (!spi->bits_per_word) { spi->bits_per_word = 8; - - status = __spi_validate_bits_per_word(spi->controller, - spi->bits_per_word); - if (status) - return status; + } else { + /* + * Some controllers may not support the default 8 bits-per-word + * so only perform the check when this is explicitly provided. + */ + status = __spi_validate_bits_per_word(spi->controller, + spi->bits_per_word); + if (status) + return status; + } if (spi->controller->max_speed_hz && (!spi->max_speed_hz || @@ -3544,10 +3548,9 @@ int spi_setup(struct spi_device *spi) } if (spi->controller->auto_runtime_pm && spi->controller->set_cs) { - status = pm_runtime_get_sync(spi->controller->dev.parent); + status = pm_runtime_resume_and_get(spi->controller->dev.parent); if (status < 0) { mutex_unlock(&spi->controller->io_mutex); - pm_runtime_put_noidle(spi->controller->dev.parent); dev_err(&spi->controller->dev, "Failed to power device: %d\n", status); return status; diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index 53a551714265..b2775d82d2d7 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -8,19 +8,18 @@ */ #include -#include #include #include #include #include #include #include +#include +#include #include +#include #include #include -#include -#include -#include #include #include @@ -46,6 +45,7 @@ static DECLARE_BITMAP(minors, N_SPI_MINORS); +static_assert(N_SPI_MINORS > 0 && N_SPI_MINORS <= 256); /* Bit masks for spi_device.mode management. Note that incorrect * settings for some settings can cause *lots* of trouble for other @@ -63,7 +63,8 @@ static DECLARE_BITMAP(minors, N_SPI_MINORS); | SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP \ | SPI_NO_CS | SPI_READY | SPI_TX_DUAL \ | SPI_TX_QUAD | SPI_TX_OCTAL | SPI_RX_DUAL \ - | SPI_RX_QUAD | SPI_RX_OCTAL) + | SPI_RX_QUAD | SPI_RX_OCTAL \ + | SPI_RX_CPHA_FLIP) struct spidev_data { dev_t devt; @@ -568,19 +569,20 @@ spidev_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) static int spidev_open(struct inode *inode, struct file *filp) { - struct spidev_data *spidev; + struct spidev_data *spidev = NULL, *iter; int status = -ENXIO; mutex_lock(&device_list_lock); - list_for_each_entry(spidev, &device_list, device_entry) { - if (spidev->devt == inode->i_rdev) { + list_for_each_entry(iter, &device_list, device_entry) { + if (iter->devt == inode->i_rdev) { status = 0; + spidev = iter; break; } } - if (status) { + if (!spidev) { pr_debug("spidev: nothing for minor %d\n", iminor(inode)); goto err_find_dev; } @@ -693,25 +695,38 @@ static const struct spi_device_id spidev_spi_ids[] = { }; MODULE_DEVICE_TABLE(spi, spidev_spi_ids); -#ifdef CONFIG_OF +/* + * spidev should never be referenced in DT without a specific compatible string, + * it is a Linux implementation thing rather than a description of the hardware. + */ +static int spidev_of_check(struct device *dev) +{ + if (device_property_match_string(dev, "compatible", "spidev") < 0) + return 0; + + dev_err(dev, "spidev listed directly in DT is not supported\n"); + return -EINVAL; +} + static const struct of_device_id spidev_dt_ids[] = { - { .compatible = "rohm,dh2228fv" }, - { .compatible = "lineartechnology,ltc2488" }, - { .compatible = "semtech,sx1301" }, - { .compatible = "lwn,bk4" }, - { .compatible = "dh,dhcom-board" }, - { .compatible = "menlo,m53cpld" }, - { .compatible = "cisco,spi-petra" }, - { .compatible = "micron,spi-authenta" }, + { .compatible = "rohm,dh2228fv", .data = &spidev_of_check }, + { .compatible = "lineartechnology,ltc2488", .data = &spidev_of_check }, + { .compatible = "semtech,sx1301", .data = &spidev_of_check }, + { .compatible = "lwn,bk4", .data = &spidev_of_check }, + { .compatible = "dh,dhcom-board", .data = &spidev_of_check }, + { .compatible = "menlo,m53cpld", .data = &spidev_of_check }, + { .compatible = "cisco,spi-petra", .data = &spidev_of_check }, + { .compatible = "micron,spi-authenta", .data = &spidev_of_check }, {}, }; MODULE_DEVICE_TABLE(of, spidev_dt_ids); -#endif - -#ifdef CONFIG_ACPI /* Dummy SPI devices not to be used in production systems */ -#define SPIDEV_ACPI_DUMMY 1 +static int spidev_acpi_check(struct device *dev) +{ + dev_warn(dev, "do not use this driver in production systems!\n"); + return 0; +} static const struct acpi_device_id spidev_acpi_ids[] = { /* @@ -720,51 +735,29 @@ static const struct acpi_device_id spidev_acpi_ids[] = { * description of the connected peripheral and they should also use * a proper driver instead of poking directly to the SPI bus. */ - { "SPT0001", SPIDEV_ACPI_DUMMY }, - { "SPT0002", SPIDEV_ACPI_DUMMY }, - { "SPT0003", SPIDEV_ACPI_DUMMY }, + { "SPT0001", (kernel_ulong_t)&spidev_acpi_check }, + { "SPT0002", (kernel_ulong_t)&spidev_acpi_check }, + { "SPT0003", (kernel_ulong_t)&spidev_acpi_check }, {}, }; MODULE_DEVICE_TABLE(acpi, spidev_acpi_ids); -static void spidev_probe_acpi(struct spi_device *spi) -{ - const struct acpi_device_id *id; - - if (!has_acpi_companion(&spi->dev)) - return; - - id = acpi_match_device(spidev_acpi_ids, &spi->dev); - if (WARN_ON(!id)) - return; - - if (id->driver_data == SPIDEV_ACPI_DUMMY) - dev_warn(&spi->dev, "do not use this driver in production systems!\n"); -} -#else -static inline void spidev_probe_acpi(struct spi_device *spi) {} -#endif - /*-------------------------------------------------------------------------*/ static int spidev_probe(struct spi_device *spi) { + int (*match)(struct device *dev); struct spidev_data *spidev; int status; unsigned long minor; - /* - * spidev should never be referenced in DT without a specific - * compatible string, it is a Linux implementation thing - * rather than a description of the hardware. - */ - if (spi->dev.of_node && of_device_is_compatible(spi->dev.of_node, "spidev")) { - dev_err(&spi->dev, "spidev listed directly in DT is not supported\n"); - return -EINVAL; + match = device_get_match_data(&spi->dev); + if (match) { + status = match(&spi->dev); + if (status) + return status; } - spidev_probe_acpi(spi); - /* Allocate driver data */ spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); if (!spidev) @@ -832,8 +825,8 @@ static void spidev_remove(struct spi_device *spi) static struct spi_driver spidev_spi_driver = { .driver = { .name = "spidev", - .of_match_table = of_match_ptr(spidev_dt_ids), - .acpi_match_table = ACPI_PTR(spidev_acpi_ids), + .of_match_table = spidev_dt_ids, + .acpi_match_table = spidev_acpi_ids, }, .probe = spidev_probe, .remove = spidev_remove, @@ -856,7 +849,6 @@ static int __init spidev_init(void) * that will key udev/mdev to add/remove /dev nodes. Last, register * the driver which manages those device numbers. */ - BUILD_BUG_ON(N_SPI_MINORS > 256); status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops); if (status < 0) return status; diff --git a/drivers/thermal/cpufreq_cooling.c b/drivers/thermal/cpufreq_cooling.c index 0bfb8eebd126..b8151d95a806 100644 --- a/drivers/thermal/cpufreq_cooling.c +++ b/drivers/thermal/cpufreq_cooling.c @@ -328,7 +328,7 @@ static inline bool em_is_sane(struct cpufreq_cooling_device *cpufreq_cdev, struct cpufreq_policy *policy; unsigned int nr_levels; - if (!em) + if (!em || em_is_artificial(em)) return false; policy = cpufreq_cdev->policy; diff --git a/drivers/thermal/devfreq_cooling.c b/drivers/thermal/devfreq_cooling.c index 4310cb342a9f..b04dcbbf721a 100644 --- a/drivers/thermal/devfreq_cooling.c +++ b/drivers/thermal/devfreq_cooling.c @@ -358,6 +358,7 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df, struct thermal_cooling_device *cdev; struct device *dev = df->dev.parent; struct devfreq_cooling_device *dfc; + struct em_perf_domain *em; char *name; int err, num_opps; @@ -367,8 +368,9 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df, dfc->devfreq = df; - dfc->em_pd = em_pd_get(dev); - if (dfc->em_pd) { + em = em_pd_get(dev); + if (em && !em_is_artificial(em)) { + dfc->em_pd = em; devfreq_cooling_ops.get_requested_power = devfreq_cooling_get_requested_power; devfreq_cooling_ops.state2power = devfreq_cooling_state2power; @@ -379,7 +381,7 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df, num_opps = em_pd_nr_perf_states(dfc->em_pd); } else { /* Backward compatibility for drivers which do not use IPA */ - dev_dbg(dev, "missing EM for cooling device\n"); + dev_dbg(dev, "missing proper EM for cooling device\n"); num_opps = dev_pm_opp_get_opp_count(dev); diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c index ad03262cca56..09e49ec8b6f4 100644 --- a/drivers/thermal/thermal_hwmon.c +++ b/drivers/thermal/thermal_hwmon.c @@ -149,8 +149,8 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) INIT_LIST_HEAD(&hwmon->tz_list); strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); strreplace(hwmon->type, '-', '_'); - hwmon->device = hwmon_device_register_with_info(&tz->device, hwmon->type, - hwmon, NULL, NULL); + hwmon->device = hwmon_device_register_for_thermal(&tz->device, + hwmon->type, hwmon); if (IS_ERR(hwmon->device)) { result = PTR_ERR(hwmon->device); goto free_mem; @@ -277,3 +277,5 @@ int devm_thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) return ret; } EXPORT_SYMBOL_GPL(devm_thermal_add_hwmon_sysfs); + +MODULE_IMPORT_NS(HWMON_THERMAL); diff --git a/fs/verity/Kconfig b/fs/verity/Kconfig index 24d1b54de807..54598cd80145 100644 --- a/fs/verity/Kconfig +++ b/fs/verity/Kconfig @@ -3,6 +3,7 @@ config FS_VERITY bool "FS Verity (read-only file-based authenticity protection)" select CRYPTO + select CRYPTO_HASH_INFO # SHA-256 is implied as it's intended to be the default hash algorithm. # To avoid bloat, other wanted algorithms must be selected explicitly. # Note that CRYPTO_SHA256 denotes the generic C implementation, but diff --git a/fs/verity/fsverity_private.h b/fs/verity/fsverity_private.h index caf9d248ec2d..629785c95007 100644 --- a/fs/verity/fsverity_private.h +++ b/fs/verity/fsverity_private.h @@ -14,7 +14,6 @@ #define pr_fmt(fmt) "fs-verity: " fmt -#include #include #include @@ -26,12 +25,6 @@ struct ahash_request; */ #define FS_VERITY_MAX_LEVELS 8 -/* - * Largest digest size among all hash algorithms supported by fs-verity. - * Currently assumed to be <= size of fsverity_descriptor::root_hash. - */ -#define FS_VERITY_MAX_DIGEST_SIZE SHA512_DIGEST_SIZE - /* A hash algorithm supported by fs-verity */ struct fsverity_hash_alg { struct crypto_ahash *tfm; /* hash tfm, allocated on demand */ diff --git a/fs/verity/measure.c b/fs/verity/measure.c index f0d7b30c62db..e99c00350c28 100644 --- a/fs/verity/measure.c +++ b/fs/verity/measure.c @@ -57,3 +57,46 @@ int fsverity_ioctl_measure(struct file *filp, void __user *_uarg) return 0; } EXPORT_SYMBOL_GPL(fsverity_ioctl_measure); + +/** + * fsverity_get_digest() - get a verity file's digest + * @inode: inode to get digest of + * @digest: (out) pointer to the digest + * @alg: (out) pointer to the hash algorithm enumeration + * + * Return the file hash algorithm and digest of an fsverity protected file. + * Assumption: before calling fsverity_get_digest(), the file must have been + * opened. + * + * Return: 0 on success, -errno on failure + */ +int fsverity_get_digest(struct inode *inode, + u8 digest[FS_VERITY_MAX_DIGEST_SIZE], + enum hash_algo *alg) +{ + const struct fsverity_info *vi; + const struct fsverity_hash_alg *hash_alg; + int i; + + vi = fsverity_get_info(inode); + if (!vi) + return -ENODATA; /* not a verity file */ + + hash_alg = vi->tree_params.hash_alg; + memset(digest, 0, FS_VERITY_MAX_DIGEST_SIZE); + + /* convert the verity hash algorithm name to a hash_algo_name enum */ + i = match_string(hash_algo_name, HASH_ALGO__LAST, hash_alg->name); + if (i < 0) + return -EINVAL; + *alg = i; + + if (WARN_ON_ONCE(hash_alg->digest_size != hash_digest_size[*alg])) + return -EINVAL; + memcpy(digest, vi->file_digest, hash_alg->digest_size); + + pr_debug("file digest %s:%*phN\n", hash_algo_name[*alg], + hash_digest_size[*alg], digest); + + return 0; +} diff --git a/include/acpi/acbuffer.h b/include/acpi/acbuffer.h index 3e8d969b22fe..8cbfcbca7b7e 100644 --- a/include/acpi/acbuffer.h +++ b/include/acpi/acbuffer.h @@ -3,7 +3,7 @@ * * Name: acbuffer.h - Support for buffers returned by ACPI predefined names * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/acconfig.h b/include/acpi/acconfig.h index 0362cbb72359..c3ae3ea88e17 100644 --- a/include/acpi/acconfig.h +++ b/include/acpi/acconfig.h @@ -3,7 +3,7 @@ * * Name: acconfig.h - Global configuration constants * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/acexcep.h b/include/acpi/acexcep.h index ea3b1c41bc79..28943c900be7 100644 --- a/include/acpi/acexcep.h +++ b/include/acpi/acexcep.h @@ -3,7 +3,7 @@ * * Name: acexcep.h - Exception codes returned by the ACPI subsystem * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/acnames.h b/include/acpi/acnames.h index 30869ab77fba..6f22e92b1744 100644 --- a/include/acpi/acnames.h +++ b/include/acpi/acnames.h @@ -3,7 +3,7 @@ * * Name: acnames.h - Global names and strings * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/acoutput.h b/include/acpi/acoutput.h index 5a3875744678..73781aae2119 100644 --- a/include/acpi/acoutput.h +++ b/include/acpi/acoutput.h @@ -3,7 +3,7 @@ * * Name: acoutput.h -- debug output * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/acpi.h b/include/acpi/acpi.h index 6f6282a862bc..416e59bcf149 100644 --- a/include/acpi/acpi.h +++ b/include/acpi/acpi.h @@ -3,7 +3,7 @@ * * Name: acpi.h - Master public include file used to interface to ACPICA * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index c4b78c21d793..772590e2eddb 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -481,6 +481,8 @@ void acpi_initialize_hp_context(struct acpi_device *adev, extern struct bus_type acpi_bus_type; int acpi_bus_for_each_dev(int (*fn)(struct device *, void *), void *data); +int acpi_dev_for_each_child(struct acpi_device *adev, + int (*fn)(struct acpi_device *, void *), void *data); /* * Events @@ -522,6 +524,7 @@ int acpi_device_fix_up_power(struct acpi_device *device); int acpi_bus_update_power(acpi_handle handle, int *state_p); int acpi_device_update_power(struct acpi_device *device, int *state_p); bool acpi_bus_power_manageable(acpi_handle handle); +void acpi_dev_power_up_children_with_adr(struct acpi_device *adev); int acpi_device_power_add_dependent(struct acpi_device *adev, struct device *dev); void acpi_device_power_remove_dependent(struct acpi_device *adev, diff --git a/include/acpi/acpiosxf.h b/include/acpi/acpiosxf.h index 690c369b717a..52844cc5eeb5 100644 --- a/include/acpi/acpiosxf.h +++ b/include/acpi/acpiosxf.h @@ -5,7 +5,7 @@ * interfaces must be implemented by OSL to interface the * ACPI components to the host operating system. * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/acpixf.h b/include/acpi/acpixf.h index 7417731472b7..67c0b9e734b6 100644 --- a/include/acpi/acpixf.h +++ b/include/acpi/acpixf.h @@ -3,7 +3,7 @@ * * Name: acpixf.h - External interfaces to the ACPI subsystem * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ @@ -12,7 +12,7 @@ /* Current ACPICA subsystem version in YYYYMMDD format */ -#define ACPI_CA_VERSION 0x20211217 +#define ACPI_CA_VERSION 0x20220331 #include #include diff --git a/include/acpi/acrestyp.h b/include/acpi/acrestyp.h index 8e2319bbd0a2..a7fb8ddb3dc6 100644 --- a/include/acpi/acrestyp.h +++ b/include/acpi/acrestyp.h @@ -3,7 +3,7 @@ * * Name: acrestyp.h - Defines, types, and structures for resource descriptors * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/actbl.h b/include/acpi/actbl.h index f9cda909f92c..c6af579f74f4 100644 --- a/include/acpi/actbl.h +++ b/include/acpi/actbl.h @@ -3,7 +3,7 @@ * * Name: actbl.h - Basic ACPI Table Definitions * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/actbl1.h b/include/acpi/actbl1.h index 159070edd031..15c78678c5d3 100644 --- a/include/acpi/actbl1.h +++ b/include/acpi/actbl1.h @@ -3,7 +3,7 @@ * * Name: actbl1.h - Additional ACPI table definitions * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ @@ -373,17 +373,21 @@ struct acpi_cedt_cfmws { u32 interleave_targets[]; }; +struct acpi_cedt_cfmws_target_element { + u32 interleave_target; +}; + /* Values for Interleave Arithmetic field above */ -#define ACPI_CEDT_CFMWS_ARITHMETIC_MODULO (0) +#define ACPI_CEDT_CFMWS_ARITHMETIC_MODULO (0) /* Values for Restrictions field above */ -#define ACPI_CEDT_CFMWS_RESTRICT_TYPE2 (1) -#define ACPI_CEDT_CFMWS_RESTRICT_TYPE3 (1<<1) -#define ACPI_CEDT_CFMWS_RESTRICT_VOLATILE (1<<2) -#define ACPI_CEDT_CFMWS_RESTRICT_PMEM (1<<3) -#define ACPI_CEDT_CFMWS_RESTRICT_FIXED (1<<4) +#define ACPI_CEDT_CFMWS_RESTRICT_TYPE2 (1) +#define ACPI_CEDT_CFMWS_RESTRICT_TYPE3 (1<<1) +#define ACPI_CEDT_CFMWS_RESTRICT_VOLATILE (1<<2) +#define ACPI_CEDT_CFMWS_RESTRICT_PMEM (1<<3) +#define ACPI_CEDT_CFMWS_RESTRICT_FIXED (1<<4) /******************************************************************************* * diff --git a/include/acpi/actbl2.h b/include/acpi/actbl2.h index 16847c8d9d5f..655102bc6d14 100644 --- a/include/acpi/actbl2.h +++ b/include/acpi/actbl2.h @@ -3,7 +3,7 @@ * * Name: actbl2.h - ACPI Table Definitions (tables not in ACPI spec) * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ @@ -25,6 +25,7 @@ * the wrong signature. */ #define ACPI_SIG_AGDI "AGDI" /* Arm Generic Diagnostic Dump and Reset Device Interface */ +#define ACPI_SIG_APMT "APMT" /* Arm Performance Monitoring Unit table */ #define ACPI_SIG_BDAT "BDAT" /* BIOS Data ACPI Table */ #define ACPI_SIG_IORT "IORT" /* IO Remapping Table */ #define ACPI_SIG_IVRS "IVRS" /* I/O Virtualization Reporting Structure */ @@ -258,6 +259,85 @@ struct acpi_table_agdi { #define ACPI_AGDI_SIGNALING_MODE (1) +/******************************************************************************* + * + * APMT - ARM Performance Monitoring Unit Table + * + * Conforms to: + * ARM Performance Monitoring Unit Architecture 1.0 Platform Design Document + * ARM DEN0117 v1.0 November 25, 2021 + * + ******************************************************************************/ + +struct acpi_table_apmt { + struct acpi_table_header header; /* Common ACPI table header */ +}; + +#define ACPI_APMT_NODE_ID_LENGTH 4 + +/* + * APMT subtables + */ +struct acpi_apmt_node { + u16 length; + u8 flags; + u8 type; + u32 id; + u64 inst_primary; + u32 inst_secondary; + u64 base_address0; + u64 base_address1; + u32 ovflw_irq; + u32 reserved; + u32 ovflw_irq_flags; + u32 proc_affinity; + u32 impl_id; +}; + +/* Masks for Flags field above */ + +#define ACPI_APMT_FLAGS_DUAL_PAGE (1<<0) +#define ACPI_APMT_FLAGS_AFFINITY (1<<1) +#define ACPI_APMT_FLAGS_ATOMIC (1<<2) + +/* Values for Flags dual page field above */ + +#define ACPI_APMT_FLAGS_DUAL_PAGE_NSUPP (0<<0) +#define ACPI_APMT_FLAGS_DUAL_PAGE_SUPP (1<<0) + +/* Values for Flags processor affinity field above */ +#define ACPI_APMT_FLAGS_AFFINITY_PROC (0<<1) +#define ACPI_APMT_FLAGS_AFFINITY_PROC_CONTAINER (1<<1) + +/* Values for Flags 64-bit atomic field above */ +#define ACPI_APMT_FLAGS_ATOMIC_NSUPP (0<<2) +#define ACPI_APMT_FLAGS_ATOMIC_SUPP (1<<2) + +/* Values for Type field above */ + +enum acpi_apmt_node_type { + ACPI_APMT_NODE_TYPE_MC = 0x00, + ACPI_APMT_NODE_TYPE_SMMU = 0x01, + ACPI_APMT_NODE_TYPE_PCIE_ROOT = 0x02, + ACPI_APMT_NODE_TYPE_ACPI = 0x03, + ACPI_APMT_NODE_TYPE_CACHE = 0x04, + ACPI_APMT_NODE_TYPE_COUNT +}; + +/* Masks for ovflw_irq_flags field above */ + +#define ACPI_APMT_OVFLW_IRQ_FLAGS_MODE (1<<0) +#define ACPI_APMT_OVFLW_IRQ_FLAGS_TYPE (1<<1) + +/* Values for ovflw_irq_flags mode field above */ + +#define ACPI_APMT_OVFLW_IRQ_FLAGS_MODE_LEVEL (0<<0) +#define ACPI_APMT_OVFLW_IRQ_FLAGS_MODE_EDGE (1<<0) + +/* Values for ovflw_irq_flags type field above */ + +#define ACPI_APMT_OVFLW_IRQ_FLAGS_TYPE_WIRED (0<<1) + /******************************************************************************* * * BDAT - BIOS Data ACPI Table @@ -277,7 +357,7 @@ struct acpi_table_bdat { * IORT - IO Remapping Table * * Conforms to "IO Remapping Table System Software on ARM Platforms", - * Document number: ARM DEN 0049E.b, Feb 2021 + * Document number: ARM DEN 0049E.d, Feb 2022 * ******************************************************************************/ @@ -374,7 +454,8 @@ struct acpi_iort_root_complex { u32 ats_attribute; u32 pci_segment_number; u8 memory_address_limit; /* Memory address size limit */ - u8 reserved[3]; /* Reserved, must be zero */ + u16 pasid_capabilities; /* PASID Capabilities */ + u8 reserved[1]; /* Reserved, must be zero */ }; /* Masks for ats_attribute field above */ @@ -383,6 +464,9 @@ struct acpi_iort_root_complex { #define ACPI_IORT_PRI_SUPPORTED (1<<1) /* The root complex PRI support */ #define ACPI_IORT_PASID_FWD_SUPPORTED (1<<2) /* The root complex PASID forward support */ +/* Masks for pasid_capabilities field above */ +#define ACPI_IORT_PASID_MAX_WIDTH (0x1F) /* Bits 0-4 */ + struct acpi_iort_smmu { u64 base_address; /* SMMU base address */ u64 span; /* Length of memory range */ @@ -458,6 +542,25 @@ struct acpi_iort_rmr { u32 rmr_offset; }; +/* Masks for Flags field above */ +#define ACPI_IORT_RMR_REMAP_PERMITTED (1) +#define ACPI_IORT_RMR_ACCESS_PRIVILEGE (1<<1) + +/* + * Macro to access the Access Attributes in flags field above: + * Access Attributes is encoded in bits 9:2 + */ +#define ACPI_IORT_RMR_ACCESS_ATTRIBUTES(flags) (((flags) >> 2) & 0xFF) + +/* Values for above Access Attributes */ + +#define ACPI_IORT_RMR_ATTR_DEVICE_NGNRNE 0x00 +#define ACPI_IORT_RMR_ATTR_DEVICE_NGNRE 0x01 +#define ACPI_IORT_RMR_ATTR_DEVICE_NGRE 0x02 +#define ACPI_IORT_RMR_ATTR_DEVICE_GRE 0x03 +#define ACPI_IORT_RMR_ATTR_NORMAL_NC 0x04 +#define ACPI_IORT_RMR_ATTR_NORMAL_IWB_OWB 0x05 + struct acpi_iort_rmr_desc { u64 base_address; u64 length; @@ -762,7 +865,8 @@ enum acpi_madt_type { ACPI_MADT_TYPE_GENERIC_REDISTRIBUTOR = 14, ACPI_MADT_TYPE_GENERIC_TRANSLATOR = 15, ACPI_MADT_TYPE_MULTIPROC_WAKEUP = 16, - ACPI_MADT_TYPE_RESERVED = 17 /* 17 and greater are reserved */ + ACPI_MADT_TYPE_RESERVED = 17, /* 17 to 0x7F are reserved */ + ACPI_MADT_TYPE_OEM_RESERVED = 0x80 /* 0x80 to 0xFF are reserved for OEM use */ }; /* @@ -978,8 +1082,8 @@ struct acpi_madt_multiproc_wakeup { u64 base_address; }; -#define ACPI_MULTIPROC_WAKEUP_MB_OS_SIZE 2032 -#define ACPI_MULTIPROC_WAKEUP_MB_FIRMWARE_SIZE 2048 +#define ACPI_MULTIPROC_WAKEUP_MB_OS_SIZE 2032 +#define ACPI_MULTIPROC_WAKEUP_MB_FIRMWARE_SIZE 2048 struct acpi_madt_multiproc_wakeup_mailbox { u16 command; @@ -992,6 +1096,12 @@ struct acpi_madt_multiproc_wakeup_mailbox { #define ACPI_MP_WAKE_COMMAND_WAKEUP 1 +/* 17: OEM data */ + +struct acpi_madt_oem_data { + u8 oem_data[0]; +}; + /* * Common flags fields for MADT subtables */ @@ -1597,7 +1707,7 @@ struct acpi_nhlt_mic_device_specific_config { /* Values for array_type_ext above */ -#define ACPI_NHLT_ARRAY_TYPE_RESERVED 0x09 // 9 and below are reserved +#define ACPI_NHLT_ARRAY_TYPE_RESERVED 0x09 /* 9 and below are reserved */ #define ACPI_NHLT_SMALL_LINEAR_2ELEMENT 0x0A #define ACPI_NHLT_BIG_LINEAR_2ELEMENT 0x0B #define ACPI_NHLT_FIRST_GEOMETRY_LINEAR_4ELEMENT 0x0C @@ -1617,17 +1727,17 @@ struct acpi_nhlt_vendor_mic_count { struct acpi_nhlt_vendor_mic_config { u8 type; u8 panel; - u16 speaker_position_distance; // mm - u16 horizontal_offset; // mm - u16 vertical_offset; // mm - u8 frequency_low_band; // 5*hz - u8 frequency_high_band; // 500*hz - u16 direction_angle; // -180 - + 180 - u16 elevation_angle; // -180 - + 180 - u16 work_vertical_angle_begin; // -180 - + 180 with 2 deg step - u16 work_vertical_angle_end; // -180 - + 180 with 2 deg step - u16 work_horizontal_angle_begin; // -180 - + 180 with 2 deg step - u16 work_horizontal_angle_end; // -180 - + 180 with 2 deg step + u16 speaker_position_distance; /* mm */ + u16 horizontal_offset; /* mm */ + u16 vertical_offset; /* mm */ + u8 frequency_low_band; /* 5*Hz */ + u8 frequency_high_band; /* 500*Hz */ + u16 direction_angle; /* -180 - + 180 */ + u16 elevation_angle; /* -180 - + 180 */ + u16 work_vertical_angle_begin; /* -180 - + 180 with 2 deg step */ + u16 work_vertical_angle_end; /* -180 - + 180 with 2 deg step */ + u16 work_horizontal_angle_begin; /* -180 - + 180 with 2 deg step */ + u16 work_horizontal_angle_end; /* -180 - + 180 with 2 deg step */ }; /* Values for Type field above */ @@ -1638,9 +1748,9 @@ struct acpi_nhlt_vendor_mic_config { #define ACPI_NHLT_MIC_SUPER_CARDIOID 3 #define ACPI_NHLT_MIC_HYPER_CARDIOID 4 #define ACPI_NHLT_MIC_8_SHAPED 5 -#define ACPI_NHLT_MIC_RESERVED6 6 // 6 is reserved +#define ACPI_NHLT_MIC_RESERVED6 6 /* 6 is reserved */ #define ACPI_NHLT_MIC_VENDOR_DEFINED 7 -#define ACPI_NHLT_MIC_RESERVED 8 // 8 and above are reserved +#define ACPI_NHLT_MIC_RESERVED 8 /* 8 and above are reserved */ /* Values for Panel field above */ @@ -1650,12 +1760,12 @@ struct acpi_nhlt_vendor_mic_config { #define ACPI_NHLT_MIC_POSITION_RIGHT 3 #define ACPI_NHLT_MIC_POSITION_FRONT 4 #define ACPI_NHLT_MIC_POSITION_BACK 5 -#define ACPI_NHLT_MIC_POSITION_RESERVED 6 // 6 and above are reserved +#define ACPI_NHLT_MIC_POSITION_RESERVED 6 /* 6 and above are reserved */ struct acpi_nhlt_vendor_mic_device_specific_config { struct acpi_nhlt_mic_device_specific_config mic_array_device_config; u8 number_of_microphones; - struct acpi_nhlt_vendor_mic_config mic_config[]; // indexed by number_of_microphones + struct acpi_nhlt_vendor_mic_config mic_config[]; /* Indexed by number_of_microphones */ }; /* Microphone SNR and Sensitivity extension */ @@ -1668,32 +1778,23 @@ struct acpi_nhlt_mic_snr_sensitivity_extension { /* Render device with feedback */ struct acpi_nhlt_render_feedback_device_specific_config { - u8 feedback_virtual_slot; // render slot in case of capture - u16 feedback_channels; // informative only + u8 feedback_virtual_slot; /* Render slot in case of capture */ + u16 feedback_channels; /* Informative only */ u16 feedback_valid_bits_per_sample; }; -/* Linux-specific structures */ +/* Non documented structures */ -struct acpi_nhlt_linux_specific_count { +struct acpi_nhlt_device_info_count { u8 structure_count; }; -struct acpi_nhlt_linux_specific_data { +struct acpi_nhlt_device_info { u8 device_id[16]; u8 device_instance_id; u8 device_port_id; }; -struct acpi_nhlt_linux_specific_data_b { - u8 specific_data[18]; -}; - -struct acpi_nhlt_table_terminator { - u32 terminator_value; - u32 terminator_signature; -}; - /******************************************************************************* * * PCCT - Platform Communications Channel Table (ACPI 5.0) @@ -2319,7 +2420,7 @@ struct acpi_table_rgrt { u16 version; u8 image_type; u8 reserved; - u8 image[0]; + u8 image[]; }; /* image_type values */ diff --git a/include/acpi/actbl3.h b/include/acpi/actbl3.h index edbf1ad8206d..7b9571e00cc4 100644 --- a/include/acpi/actbl3.h +++ b/include/acpi/actbl3.h @@ -3,7 +3,7 @@ * * Name: actbl3.h - ACPI Table Definitions * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/actypes.h b/include/acpi/actypes.h index 02c1fa16e638..3491e454b2ab 100644 --- a/include/acpi/actypes.h +++ b/include/acpi/actypes.h @@ -3,7 +3,7 @@ * * Name: actypes.h - Common data types for the entire ACPI subsystem * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ @@ -539,14 +539,14 @@ typedef u64 acpi_integer; * Can be used with access_width of struct acpi_generic_address and access_size of * struct acpi_resource_generic_register. */ -#define ACPI_ACCESS_BIT_SHIFT 2 -#define ACPI_ACCESS_BYTE_SHIFT -1 -#define ACPI_ACCESS_BIT_MAX (31 - ACPI_ACCESS_BIT_SHIFT) -#define ACPI_ACCESS_BYTE_MAX (31 - ACPI_ACCESS_BYTE_SHIFT) -#define ACPI_ACCESS_BIT_DEFAULT (8 - ACPI_ACCESS_BIT_SHIFT) -#define ACPI_ACCESS_BYTE_DEFAULT (8 - ACPI_ACCESS_BYTE_SHIFT) -#define ACPI_ACCESS_BIT_WIDTH(size) (1 << ((size) + ACPI_ACCESS_BIT_SHIFT)) -#define ACPI_ACCESS_BYTE_WIDTH(size) (1 << ((size) + ACPI_ACCESS_BYTE_SHIFT)) +#define ACPI_ACCESS_BIT_SHIFT 2 +#define ACPI_ACCESS_BYTE_SHIFT -1 +#define ACPI_ACCESS_BIT_MAX (31 - ACPI_ACCESS_BIT_SHIFT) +#define ACPI_ACCESS_BYTE_MAX (31 - ACPI_ACCESS_BYTE_SHIFT) +#define ACPI_ACCESS_BIT_DEFAULT (8 - ACPI_ACCESS_BIT_SHIFT) +#define ACPI_ACCESS_BYTE_DEFAULT (8 - ACPI_ACCESS_BYTE_SHIFT) +#define ACPI_ACCESS_BIT_WIDTH(size) (1 << ((size) + ACPI_ACCESS_BIT_SHIFT)) +#define ACPI_ACCESS_BYTE_WIDTH(size) (1 << ((size) + ACPI_ACCESS_BYTE_SHIFT)) /******************************************************************************* * @@ -1303,6 +1303,7 @@ typedef enum { #define ACPI_OSI_WIN_10_RS5 0x13 #define ACPI_OSI_WIN_10_19H1 0x14 #define ACPI_OSI_WIN_10_20H1 0x15 +#define ACPI_OSI_WIN_11 0x16 /* Definitions of getopt */ diff --git a/include/acpi/acuuid.h b/include/acpi/acuuid.h index bc24388ce94e..8f1e7c489df5 100644 --- a/include/acpi/acuuid.h +++ b/include/acpi/acuuid.h @@ -3,7 +3,7 @@ * * Name: acuuid.h - ACPI-related UUID/GUID definitions * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/apei.h b/include/acpi/apei.h index afaca3a075e8..dc60f7db5524 100644 --- a/include/acpi/apei.h +++ b/include/acpi/apei.h @@ -46,6 +46,8 @@ int erst_get_record_id_next(int *pos, u64 *record_id); void erst_get_record_id_end(void); ssize_t erst_read(u64 record_id, struct cper_record_header *record, size_t buflen); +ssize_t erst_read_record(u64 record_id, struct cper_record_header *record, + size_t buflen, size_t recordlen, const guid_t *creatorid); int erst_clear(u64 record_id); int arch_apei_enable_cmcff(struct acpi_hest_header *hest_hdr, void *data); diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index 92b7ea8d8f5e..c6108581d97d 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -141,6 +141,7 @@ extern int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls); extern int cppc_set_enable(int cpu, bool enable); extern int cppc_get_perf_caps(int cpu, struct cppc_perf_caps *caps); extern bool acpi_cpc_valid(void); +extern bool cppc_allow_fast_switch(void); extern int acpi_get_psd_map(unsigned int cpu, struct cppc_cpudata *cpu_data); extern unsigned int cppc_get_transition_latency(int cpu); extern bool cpc_ffh_supported(void); @@ -175,6 +176,10 @@ static inline bool acpi_cpc_valid(void) { return false; } +static inline bool cppc_allow_fast_switch(void) +{ + return false; +} static inline unsigned int cppc_get_transition_latency(int cpu) { return CPUFREQ_ETERNAL; diff --git a/include/acpi/platform/acenv.h b/include/acpi/platform/acenv.h index e8958e0d1646..03eb3d977075 100644 --- a/include/acpi/platform/acenv.h +++ b/include/acpi/platform/acenv.h @@ -3,7 +3,7 @@ * * Name: acenv.h - Host and compiler configuration * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/platform/acenvex.h b/include/acpi/platform/acenvex.h index 277fe2fa4d9b..3a6b1db9a984 100644 --- a/include/acpi/platform/acenvex.h +++ b/include/acpi/platform/acenvex.h @@ -3,7 +3,7 @@ * * Name: acenvex.h - Extra host and compiler configuration * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/platform/acgcc.h b/include/acpi/platform/acgcc.h index 33ad282bd338..ac80111f503c 100644 --- a/include/acpi/platform/acgcc.h +++ b/include/acpi/platform/acgcc.h @@ -3,7 +3,7 @@ * * Name: acgcc.h - GCC specific defines, etc. * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/platform/acgccex.h b/include/acpi/platform/acgccex.h index 738d52865e0a..302ea1b724b9 100644 --- a/include/acpi/platform/acgccex.h +++ b/include/acpi/platform/acgccex.h @@ -3,7 +3,7 @@ * * Name: acgccex.h - Extra GCC specific defines, etc. * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/platform/acintel.h b/include/acpi/platform/acintel.h index 550fe9a8cd6c..85b1ae86ee63 100644 --- a/include/acpi/platform/acintel.h +++ b/include/acpi/platform/acintel.h @@ -3,7 +3,7 @@ * * Name: acintel.h - VC specific defines, etc. * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/platform/aclinux.h b/include/acpi/platform/aclinux.h index cec41e004ecf..a5550dd4d507 100644 --- a/include/acpi/platform/aclinux.h +++ b/include/acpi/platform/aclinux.h @@ -3,7 +3,7 @@ * * Name: aclinux.h - OS specific defines, etc. for Linux * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/acpi/platform/aclinuxex.h b/include/acpi/platform/aclinuxex.h index 5f642b07ad64..28c72744decf 100644 --- a/include/acpi/platform/aclinuxex.h +++ b/include/acpi/platform/aclinuxex.h @@ -3,7 +3,7 @@ * * Name: aclinuxex.h - Extra OS specific defines, etc. for Linux * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/include/keys/system_keyring.h b/include/keys/system_keyring.h index 2419a735420f..91e080efb918 100644 --- a/include/keys/system_keyring.h +++ b/include/keys/system_keyring.h @@ -10,6 +10,13 @@ #include +enum blacklist_hash_type { + /* TBSCertificate hash */ + BLACKLIST_HASH_X509_TBS = 1, + /* Raw data hash */ + BLACKLIST_HASH_BINARY = 2, +}; + #ifdef CONFIG_SYSTEM_TRUSTED_KEYRING extern int restrict_link_by_builtin_trusted(struct key *keyring, @@ -54,13 +61,14 @@ static inline void __init set_machine_trusted_keys(struct key *keyring) extern struct pkcs7_message *pkcs7; #ifdef CONFIG_SYSTEM_BLACKLIST_KEYRING -extern int mark_hash_blacklisted(const char *hash); +extern int mark_hash_blacklisted(const u8 *hash, size_t hash_len, + enum blacklist_hash_type hash_type); extern int is_hash_blacklisted(const u8 *hash, size_t hash_len, - const char *type); + enum blacklist_hash_type hash_type); extern int is_binary_blacklisted(const u8 *hash, size_t hash_len); #else static inline int is_hash_blacklisted(const u8 *hash, size_t hash_len, - const char *type) + enum blacklist_hash_type hash_type) { return 0; } diff --git a/include/keys/trusted-type.h b/include/keys/trusted-type.h index d89fa2579ac0..4eb64548a74f 100644 --- a/include/keys/trusted-type.h +++ b/include/keys/trusted-type.h @@ -64,7 +64,7 @@ struct trusted_key_ops { /* Unseal a key. */ int (*unseal)(struct trusted_key_payload *p, char *datablob); - /* Get a randomized key. */ + /* Optional: Get a randomized key. */ int (*get_random)(unsigned char *key, size_t key_len); /* Exit key interface. */ diff --git a/include/keys/trusted_caam.h b/include/keys/trusted_caam.h new file mode 100644 index 000000000000..73fe2f32f65e --- /dev/null +++ b/include/keys/trusted_caam.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021 Pengutronix, Ahmad Fatoum + */ + +#ifndef __CAAM_TRUSTED_KEY_H +#define __CAAM_TRUSTED_KEY_H + +extern struct trusted_key_ops trusted_key_caam_ops; + +#endif diff --git a/include/linux/acpi.h b/include/linux/acpi.h index d7136d13aa44..03465db16b68 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -574,6 +574,7 @@ acpi_status acpi_run_osc(acpi_handle handle, struct acpi_osc_context *context); #define OSC_SB_OSLPI_SUPPORT 0x00000100 #define OSC_SB_CPC_DIVERSE_HIGH_SUPPORT 0x00001000 #define OSC_SB_GENERIC_INITIATOR_SUPPORT 0x00002000 +#define OSC_SB_CPC_FLEXIBLE_ADR_SPACE 0x00004000 #define OSC_SB_NATIVE_USB4_SUPPORT 0x00040000 #define OSC_SB_PRM_SUPPORT 0x00200000 @@ -581,6 +582,7 @@ extern bool osc_sb_apei_support_acked; extern bool osc_pc_lpi_support_confirmed; extern bool osc_sb_native_usb4_support_confirmed; extern bool osc_sb_cppc_not_supported; +extern bool osc_cpc_flexible_adr_space_confirmed; /* USB4 Capabilities */ #define OSC_USB_USB3_TUNNELING 0x00000001 diff --git a/include/linux/amba/mmci.h b/include/linux/amba/mmci.h index c92ebc39fc1f..6f96dc2209c0 100644 --- a/include/linux/amba/mmci.h +++ b/include/linux/amba/mmci.h @@ -13,17 +13,11 @@ * @ocr_mask: available voltages on the 4 pins from the block, this * is ignored if a regulator is used, see the MMC_VDD_* masks in * mmc/host.h - * @ios_handler: a callback function to act on specfic ios changes, - * used for example to control a levelshifter - * mask into a value to be binary (or set some other custom bits - * in MMCIPWR) or:ed and written into the MMCIPWR register of the - * block. May also control external power based on the power_mode. * @status: if no GPIO line was given to the block in this function will * be called to determine whether a card is present in the MMC slot or not */ struct mmci_platform_data { unsigned int ocr_mask; - int (*ios_handler)(struct device *, struct mmc_ios *); unsigned int (*status)(struct device *); }; diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h index 142474b4af96..dc10bee75a72 100644 --- a/include/linux/devfreq.h +++ b/include/linux/devfreq.h @@ -38,6 +38,7 @@ enum devfreq_timer { struct devfreq; struct devfreq_governor; +struct devfreq_cpu_data; struct thermal_cooling_device; /** @@ -288,6 +289,11 @@ struct devfreq_simple_ondemand_data { #endif #if IS_ENABLED(CONFIG_DEVFREQ_GOV_PASSIVE) +enum devfreq_parent_dev_type { + DEVFREQ_PARENT_DEV, + CPUFREQ_PARENT_DEV, +}; + /** * struct devfreq_passive_data - ``void *data`` fed to struct devfreq * and devfreq_add_device @@ -299,8 +305,11 @@ struct devfreq_simple_ondemand_data { * using governors except for passive governor. * If the devfreq device has the specific method to decide * the next frequency, should use this callback. - * @this: the devfreq instance of own device. - * @nb: the notifier block for DEVFREQ_TRANSITION_NOTIFIER list + * @parent_type: the parent type of the device. + * @this: the devfreq instance of own device. + * @nb: the notifier block for DEVFREQ_TRANSITION_NOTIFIER or + * CPUFREQ_TRANSITION_NOTIFIER list. + * @cpu_data_list: the list of cpu frequency data for all cpufreq_policy. * * The devfreq_passive_data have to set the devfreq instance of parent * device with governors except for the passive governor. But, don't need to @@ -314,9 +323,13 @@ struct devfreq_passive_data { /* Optional callback to decide the next frequency of passvice device */ int (*get_target_freq)(struct devfreq *this, unsigned long *freq); + /* Should set the type of parent device */ + enum devfreq_parent_dev_type parent_type; + /* For passive governor's internal use. Don't need to set them */ struct devfreq *this; struct notifier_block nb; + struct list_head cpu_data_list; }; #endif diff --git a/include/linux/energy_model.h b/include/linux/energy_model.h index 9f3c400bc52d..8419bffb4398 100644 --- a/include/linux/energy_model.h +++ b/include/linux/energy_model.h @@ -67,11 +67,16 @@ struct em_perf_domain { * * EM_PERF_DOMAIN_SKIP_INEFFICIENCIES: Skip inefficient states when estimating * energy consumption. + * + * EM_PERF_DOMAIN_ARTIFICIAL: The power values are artificial and might be + * created by platform missing real power information */ #define EM_PERF_DOMAIN_MILLIWATTS BIT(0) #define EM_PERF_DOMAIN_SKIP_INEFFICIENCIES BIT(1) +#define EM_PERF_DOMAIN_ARTIFICIAL BIT(2) #define em_span_cpus(em) (to_cpumask((em)->cpus)) +#define em_is_artificial(em) ((em)->flags & EM_PERF_DOMAIN_ARTIFICIAL) #ifdef CONFIG_ENERGY_MODEL #define EM_MAX_POWER 0xFFFF @@ -96,11 +101,11 @@ struct em_data_callback { /** * active_power() - Provide power at the next performance state of * a device + * @dev : Device for which we do this operation (can be a CPU) * @power : Active power at the performance state * (modified) * @freq : Frequency at the performance state in kHz * (modified) - * @dev : Device for which we do this operation (can be a CPU) * * active_power() must find the lowest performance state of 'dev' above * 'freq' and update 'power' and 'freq' to the matching active power @@ -112,11 +117,32 @@ struct em_data_callback { * * Return 0 on success. */ - int (*active_power)(unsigned long *power, unsigned long *freq, - struct device *dev); + int (*active_power)(struct device *dev, unsigned long *power, + unsigned long *freq); + + /** + * get_cost() - Provide the cost at the given performance state of + * a device + * @dev : Device for which we do this operation (can be a CPU) + * @freq : Frequency at the performance state in kHz + * @cost : The cost value for the performance state + * (modified) + * + * In case of CPUs, the cost is the one of a single CPU in the domain. + * It is expected to fit in the [0, EM_MAX_POWER] range due to internal + * usage in EAS calculation. + * + * Return 0 on success, or appropriate error value in case of failure. + */ + int (*get_cost)(struct device *dev, unsigned long freq, + unsigned long *cost); }; -#define EM_DATA_CB(_active_power_cb) { .active_power = &_active_power_cb } #define EM_SET_ACTIVE_POWER_CB(em_cb, cb) ((em_cb).active_power = cb) +#define EM_ADV_DATA_CB(_active_power_cb, _cost_cb) \ + { .active_power = _active_power_cb, \ + .get_cost = _cost_cb } +#define EM_DATA_CB(_active_power_cb) \ + EM_ADV_DATA_CB(_active_power_cb, NULL) struct em_perf_domain *em_cpu_get(int cpu); struct em_perf_domain *em_pd_get(struct device *dev); @@ -264,6 +290,7 @@ static inline int em_pd_nr_perf_states(struct em_perf_domain *pd) #else struct em_data_callback {}; +#define EM_ADV_DATA_CB(_active_power_cb, _cost_cb) { } #define EM_DATA_CB(_active_power_cb) { } #define EM_SET_ACTIVE_POWER_CB(em_cb, cb) do { } while (0) diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h index 03330d947d56..84cddd901b1b 100644 --- a/include/linux/fsverity.h +++ b/include/linux/fsverity.h @@ -12,8 +12,16 @@ #define _LINUX_FSVERITY_H #include +#include +#include #include +/* + * Largest digest size among all hash algorithms supported by fs-verity. + * Currently assumed to be <= size of fsverity_descriptor::root_hash. + */ +#define FS_VERITY_MAX_DIGEST_SIZE SHA512_DIGEST_SIZE + /* Verity operations for filesystems */ struct fsverity_operations { @@ -131,6 +139,9 @@ int fsverity_ioctl_enable(struct file *filp, const void __user *arg); /* measure.c */ int fsverity_ioctl_measure(struct file *filp, void __user *arg); +int fsverity_get_digest(struct inode *inode, + u8 digest[FS_VERITY_MAX_DIGEST_SIZE], + enum hash_algo *alg); /* open.c */ @@ -170,6 +181,13 @@ static inline int fsverity_ioctl_measure(struct file *filp, void __user *arg) return -EOPNOTSUPP; } +static inline int fsverity_get_digest(struct inode *inode, + u8 digest[FS_VERITY_MAX_DIGEST_SIZE], + enum hash_algo *alg) +{ + return -EOPNOTSUPP; +} + /* open.c */ static inline int fsverity_file_open(struct inode *inode, struct file *filp) diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index eba380b76d15..14325f93c6b2 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -450,6 +450,9 @@ hwmon_device_register_with_info(struct device *dev, const struct hwmon_chip_info *info, const struct attribute_group **extra_groups); struct device * +hwmon_device_register_for_thermal(struct device *dev, const char *name, + void *drvdata); +struct device * devm_hwmon_device_register_with_info(struct device *dev, const char *name, void *drvdata, const struct hwmon_chip_info *info, @@ -461,6 +464,9 @@ void devm_hwmon_device_unregister(struct device *dev); int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel); +char *hwmon_sanitize_name(const char *name); +char *devm_hwmon_sanitize_name(struct device *dev, const char *name); + /** * hwmon_is_bad_char - Is the char invalid in a hwmon name * @ch: the char to be considered diff --git a/include/linux/ipmi.h b/include/linux/ipmi.h index 163831a087ef..a1c9c0d48ebf 100644 --- a/include/linux/ipmi.h +++ b/include/linux/ipmi.h @@ -72,6 +72,11 @@ struct ipmi_recv_msg { unsigned char msg_data[IPMI_MAX_MSG_LENGTH]; }; +#define INIT_IPMI_RECV_MSG(done_handler) \ +{ \ + .done = done_handler \ +} + /* Allocate and free the receive message. */ void ipmi_free_recv_msg(struct ipmi_recv_msg *msg); diff --git a/include/linux/ipmi_smi.h b/include/linux/ipmi_smi.h index 9277d21c2690..5d69820d8b02 100644 --- a/include/linux/ipmi_smi.h +++ b/include/linux/ipmi_smi.h @@ -125,6 +125,12 @@ struct ipmi_smi_msg { void (*done)(struct ipmi_smi_msg *msg); }; +#define INIT_IPMI_SMI_MSG(done_handler) \ +{ \ + .done = done_handler, \ + .type = IPMI_SMI_MSG_TYPE_NORMAL \ +} + struct ipmi_smi_handlers { struct module *owner; diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index db924fe379c9..eafa1d2489fd 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -100,7 +100,7 @@ LSM_HOOK(int, 0, path_link, struct dentry *old_dentry, const struct path *new_dir, struct dentry *new_dentry) LSM_HOOK(int, 0, path_rename, const struct path *old_dir, struct dentry *old_dentry, const struct path *new_dir, - struct dentry *new_dentry) + struct dentry *new_dentry, unsigned int flags) LSM_HOOK(int, 0, path_chmod, const struct path *path, umode_t mode) LSM_HOOK(int, 0, path_chown, const struct path *path, kuid_t uid, kgid_t gid) LSM_HOOK(int, 0, path_chroot, const struct path *path) diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 47cdf3fbecef..91c8146649f5 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -358,6 +358,7 @@ * @old_dentry contains the dentry structure of the old link. * @new_dir contains the path structure for parent of the new link. * @new_dentry contains the dentry structure of the new link. + * @flags may contain rename options such as RENAME_EXCHANGE. * Return 0 if permission is granted. * @path_chmod: * Check for permission to change a mode of the file @path. The new diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index de5c64bbdb72..6efec0b9820c 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -176,7 +176,7 @@ int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries); int mmc_hw_reset(struct mmc_card *card); -int mmc_sw_reset(struct mmc_host *host); +int mmc_sw_reset(struct mmc_card *card); void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card); #endif /* LINUX_MMC_CORE_H */ diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 7afb57cab00b..c193c50ccd78 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -181,7 +181,7 @@ struct mmc_host_ops { unsigned int max_dtr, int host_drv, int card_drv, int *drv_type); /* Reset the eMMC card via RST_n */ - void (*hw_reset)(struct mmc_host *host); + void (*card_hw_reset)(struct mmc_host *host); void (*card_event)(struct mmc_host *host); /* diff --git a/include/linux/mtd/cfi.h b/include/linux/mtd/cfi.h index fd1ecb821106..d88bb56c18e2 100644 --- a/include/linux/mtd/cfi.h +++ b/include/linux/mtd/cfi.h @@ -286,6 +286,7 @@ struct cfi_private { map_word sector_erase_cmd; unsigned long chipshift; /* Because they're of the same type */ const char *im_name; /* inter_module name for cmdset_setup */ + unsigned long quirks; struct flchip chips[]; /* per-chip data structure for each chip */ }; diff --git a/drivers/mtd/nand/raw/mtk_ecc.h b/include/linux/mtd/nand-ecc-mtk.h similarity index 100% rename from drivers/mtd/nand/raw/mtk_ecc.h rename to include/linux/mtd/nand-ecc-mtk.h diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 5e25a7b75ae2..1ede4c89805a 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -47,8 +47,6 @@ #define SPINOR_OP_RDID 0x9f /* Read JEDEC ID */ #define SPINOR_OP_RDSFDP 0x5a /* Read SFDP */ #define SPINOR_OP_RDCR 0x35 /* Read configuration register */ -#define SPINOR_OP_RDEAR 0xc8 /* Read Extended Address Register */ -#define SPINOR_OP_WREAR 0xc5 /* Write Extended Address Register */ #define SPINOR_OP_SRSTEN 0x66 /* Software Reset Enable */ #define SPINOR_OP_SRST 0x99 /* Software Reset */ #define SPINOR_OP_GBULK 0x98 /* Global Block Unlock */ @@ -365,6 +363,7 @@ struct spi_nor_flash_parameter; * @write_proto: the SPI protocol for write operations * @reg_proto: the SPI protocol for read_reg/write_reg/erase operations * @sfdp: the SFDP data of the flash + * @debugfs_root: pointer to the debugfs directory * @controller_ops: SPI NOR controller driver specific operations. * @params: [FLASH-SPECIFIC] SPI NOR flash parameters and settings. * The structure includes legacy flash parameters and @@ -394,6 +393,7 @@ struct spi_nor { u32 flags; enum spi_nor_cmd_ext cmd_ext_type; struct sfdp *sfdp; + struct dentry *debugfs_root; const struct spi_nor_controller_ops *controller_ops; diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 3aa28240a77f..5584d3bb6556 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -266,6 +266,7 @@ extern const struct spinand_manufacturer micron_spinand_manufacturer; extern const struct spinand_manufacturer paragon_spinand_manufacturer; extern const struct spinand_manufacturer toshiba_spinand_manufacturer; extern const struct spinand_manufacturer winbond_spinand_manufacturer; +extern const struct spinand_manufacturer xtx_spinand_manufacturer; /** * struct spinand_op_variants - SPI NAND operation variants diff --git a/include/linux/mmc/sh_mmcif.h b/include/linux/platform_data/sh_mmcif.h similarity index 99% rename from include/linux/mmc/sh_mmcif.h rename to include/linux/platform_data/sh_mmcif.h index e25533b95d9f..6eb914f958f9 100644 --- a/include/linux/mmc/sh_mmcif.h +++ b/include/linux/platform_data/sh_mmcif.h @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * include/linux/mmc/sh_mmcif.h - * * platform data for eMMC driver * * Copyright (C) 2010 Renesas Solutions Corp. diff --git a/include/linux/pm.h b/include/linux/pm.h index e65b3ab28377..ffe941958501 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -368,13 +368,13 @@ const struct dev_pm_ops name = { \ #ifdef CONFIG_PM #define _EXPORT_DEV_PM_OPS(name, suspend_fn, resume_fn, runtime_suspend_fn, \ - runtime_resume_fn, idle_fn, sec) \ + runtime_resume_fn, idle_fn, sec, ns) \ _DEFINE_DEV_PM_OPS(name, suspend_fn, resume_fn, runtime_suspend_fn, \ runtime_resume_fn, idle_fn); \ - _EXPORT_SYMBOL(name, sec) + __EXPORT_SYMBOL(name, sec, ns) #else #define _EXPORT_DEV_PM_OPS(name, suspend_fn, resume_fn, runtime_suspend_fn, \ - runtime_resume_fn, idle_fn, sec) \ + runtime_resume_fn, idle_fn, sec, ns) \ static __maybe_unused _DEFINE_DEV_PM_OPS(__static_##name, suspend_fn, \ resume_fn, runtime_suspend_fn, \ runtime_resume_fn, idle_fn) @@ -391,9 +391,13 @@ static __maybe_unused _DEFINE_DEV_PM_OPS(__static_##name, suspend_fn, \ _DEFINE_DEV_PM_OPS(name, suspend_fn, resume_fn, NULL, NULL, NULL) #define EXPORT_SIMPLE_DEV_PM_OPS(name, suspend_fn, resume_fn) \ - _EXPORT_DEV_PM_OPS(name, suspend_fn, resume_fn, NULL, NULL, NULL, "") + _EXPORT_DEV_PM_OPS(name, suspend_fn, resume_fn, NULL, NULL, NULL, "", "") #define EXPORT_GPL_SIMPLE_DEV_PM_OPS(name, suspend_fn, resume_fn) \ - _EXPORT_DEV_PM_OPS(name, suspend_fn, resume_fn, NULL, NULL, NULL, "_gpl") + _EXPORT_DEV_PM_OPS(name, suspend_fn, resume_fn, NULL, NULL, NULL, "_gpl", "") +#define EXPORT_NS_SIMPLE_DEV_PM_OPS(name, suspend_fn, resume_fn, ns) \ + _EXPORT_DEV_PM_OPS(name, suspend_fn, resume_fn, NULL, NULL, NULL, "", #ns) +#define EXPORT_NS_GPL_SIMPLE_DEV_PM_OPS(name, suspend_fn, resume_fn, ns) \ + _EXPORT_DEV_PM_OPS(name, suspend_fn, resume_fn, NULL, NULL, NULL, "_gpl", #ns) /* Deprecated. Use DEFINE_SIMPLE_DEV_PM_OPS() instead. */ #define SIMPLE_DEV_PM_OPS(name, suspend_fn, resume_fn) \ diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 67017c9390c8..ebc351698090 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -91,6 +91,14 @@ struct gpd_dev_ops { int (*stop)(struct device *dev); }; +struct genpd_governor_data { + s64 max_off_time_ns; + bool max_off_time_changed; + ktime_t next_wakeup; + bool cached_power_down_ok; + bool cached_power_down_state_idx; +}; + struct genpd_power_state { s64 power_off_latency_ns; s64 power_on_latency_ns; @@ -98,7 +106,7 @@ struct genpd_power_state { u64 usage; u64 rejected; struct fwnode_handle *fwnode; - ktime_t idle_time; + u64 idle_time; void *data; }; @@ -114,6 +122,7 @@ struct generic_pm_domain { struct list_head child_links; /* Links with PM domain as a child */ struct list_head dev_list; /* List of devices */ struct dev_power_governor *gov; + struct genpd_governor_data *gd; /* Data used by a genpd governor. */ struct work_struct power_off_work; struct fwnode_handle *provider; /* Identity of the domain provider */ bool has_provider; @@ -134,11 +143,6 @@ struct generic_pm_domain { int (*set_performance_state)(struct generic_pm_domain *genpd, unsigned int state); struct gpd_dev_ops dev_ops; - s64 max_off_time_ns; /* Maximum allowed "suspended" time. */ - ktime_t next_wakeup; /* Maintained by the domain governor */ - bool max_off_time_changed; - bool cached_power_down_ok; - bool cached_power_down_state_idx; int (*attach_dev)(struct generic_pm_domain *domain, struct device *dev); void (*detach_dev)(struct generic_pm_domain *domain, @@ -149,8 +153,8 @@ struct generic_pm_domain { unsigned int state_count); unsigned int state_count; /* number of states */ unsigned int state_idx; /* state that genpd will go to when off */ - ktime_t on_time; - ktime_t accounting_time; + u64 on_time; + u64 accounting_time; const struct genpd_lock_ops *lock_ops; union { struct mutex mlock; @@ -182,6 +186,7 @@ struct gpd_timing_data { s64 suspend_latency_ns; s64 resume_latency_ns; s64 effective_constraint_ns; + ktime_t next_wakeup; bool constraint_changed; bool cached_suspend_ok; }; @@ -193,14 +198,13 @@ struct pm_domain_data { struct generic_pm_domain_data { struct pm_domain_data base; - struct gpd_timing_data td; + struct gpd_timing_data *td; struct notifier_block nb; struct notifier_block *power_nb; int cpu; unsigned int performance_state; unsigned int default_pstate; unsigned int rpm_pstate; - ktime_t next_wakeup; void *data; }; diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h index 2bff6a10095d..9e4d056967c6 100644 --- a/include/linux/pm_runtime.h +++ b/include/linux/pm_runtime.h @@ -41,10 +41,16 @@ #define EXPORT_RUNTIME_DEV_PM_OPS(name, suspend_fn, resume_fn, idle_fn) \ _EXPORT_DEV_PM_OPS(name, pm_runtime_force_suspend, pm_runtime_force_resume, \ - suspend_fn, resume_fn, idle_fn, "") + suspend_fn, resume_fn, idle_fn, "", "") #define EXPORT_GPL_RUNTIME_DEV_PM_OPS(name, suspend_fn, resume_fn, idle_fn) \ _EXPORT_DEV_PM_OPS(name, pm_runtime_force_suspend, pm_runtime_force_resume, \ - suspend_fn, resume_fn, idle_fn, "_gpl") + suspend_fn, resume_fn, idle_fn, "_gpl", "") +#define EXPORT_NS_RUNTIME_DEV_PM_OPS(name, suspend_fn, resume_fn, idle_fn, ns) \ + _EXPORT_DEV_PM_OPS(name, pm_runtime_force_suspend, pm_runtime_force_resume, \ + suspend_fn, resume_fn, idle_fn, "", #ns) +#define EXPORT_NS_GPL_RUNTIME_DEV_PM_OPS(name, suspend_fn, resume_fn, idle_fn, ns) \ + _EXPORT_DEV_PM_OPS(name, pm_runtime_force_suspend, pm_runtime_force_resume, \ + suspend_fn, resume_fn, idle_fn, "_gpl", #ns) #ifdef CONFIG_PM extern struct workqueue_struct *pm_wq; diff --git a/include/linux/polynomial.h b/include/linux/polynomial.h new file mode 100644 index 000000000000..9e074a0bb6fa --- /dev/null +++ b/include/linux/polynomial.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + */ + +#ifndef _POLYNOMIAL_H +#define _POLYNOMIAL_H + +/* + * struct polynomial_term - one term descriptor of a polynomial + * @deg: degree of the term. + * @coef: multiplication factor of the term. + * @divider: distributed divider per each degree. + * @divider_leftover: divider leftover, which couldn't be redistributed. + */ +struct polynomial_term { + unsigned int deg; + long coef; + long divider; + long divider_leftover; +}; + +/* + * struct polynomial - a polynomial descriptor + * @total_divider: total data divider. + * @terms: polynomial terms, last term must have degree of 0 + */ +struct polynomial { + long total_divider; + struct polynomial_term terms[]; +}; + +long polynomial_calc(const struct polynomial *poly, long data); + +#endif diff --git a/include/linux/regmap.h b/include/linux/regmap.h index de81a94d7b30..8952fa3d0d59 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -299,6 +299,12 @@ typedef void (*regmap_unlock)(void *); * if the function require special handling with lock and reg * handling and the operation cannot be represented as a simple * update_bits operation on a bus such as SPI, I2C, etc. + * @read: Optional callback that if filled will be used to perform all the + * bulk reads from the registers. Data is returned in the buffer used + * to transmit data. + * @write: Same as above for writing. + * @max_raw_read: Max raw read size that can be used on the device. + * @max_raw_write: Max raw write size that can be used on the device. * @fast_io: Register IO is fast. Use a spinlock instead of a mutex * to perform locking. This field is ignored if custom lock/unlock * functions are used (see fields lock/unlock of struct regmap_config). @@ -385,6 +391,12 @@ struct regmap_config { int (*reg_write)(void *context, unsigned int reg, unsigned int val); int (*reg_update_bits)(void *context, unsigned int reg, unsigned int mask, unsigned int val); + /* Bulk read/write */ + int (*read)(void *context, const void *reg_buf, size_t reg_size, + void *val_buf, size_t val_size); + int (*write)(void *context, const void *data, size_t count); + size_t max_raw_read; + size_t max_raw_write; bool fast_io; diff --git a/include/linux/regulator/mt6358-regulator.h b/include/linux/regulator/mt6358-regulator.h index 1cc304946d09..bdcf83cd719e 100644 --- a/include/linux/regulator/mt6358-regulator.h +++ b/include/linux/regulator/mt6358-regulator.h @@ -48,9 +48,54 @@ enum { MT6358_ID_VLDO28, MT6358_ID_VAUD28, MT6358_ID_VSIM2, + MT6358_ID_VCORE_SSHUB, + MT6358_ID_VSRAM_OTHERS_SSHUB, MT6358_ID_RG_MAX, }; +enum { + MT6366_ID_VDRAM1 = 0, + MT6366_ID_VCORE, + MT6366_ID_VPA, + MT6366_ID_VPROC11, + MT6366_ID_VPROC12, + MT6366_ID_VGPU, + MT6366_ID_VS2, + MT6366_ID_VMODEM, + MT6366_ID_VS1, + MT6366_ID_VDRAM2, + MT6366_ID_VSIM1, + MT6366_ID_VIBR, + MT6366_ID_VRF12, + MT6366_ID_VIO18, + MT6366_ID_VUSB, + MT6366_ID_VCN18, + MT6366_ID_VFE28, + MT6366_ID_VSRAM_PROC11, + MT6366_ID_VCN28, + MT6366_ID_VSRAM_OTHERS, + MT6366_ID_VSRAM_GPU, + MT6366_ID_VXO22, + MT6366_ID_VEFUSE, + MT6366_ID_VAUX18, + MT6366_ID_VMCH, + MT6366_ID_VBIF28, + MT6366_ID_VSRAM_PROC12, + MT6366_ID_VEMC, + MT6366_ID_VIO28, + MT6366_ID_VA12, + MT6366_ID_VRF18, + MT6366_ID_VCN33_BT, + MT6366_ID_VCN33_WIFI, + MT6366_ID_VMC, + MT6366_ID_VAUD28, + MT6366_ID_VSIM2, + MT6366_ID_VCORE_SSHUB, + MT6366_ID_VSRAM_OTHERS_SSHUB, + MT6366_ID_RG_MAX, +}; + #define MT6358_MAX_REGULATOR MT6358_ID_RG_MAX +#define MT6366_MAX_REGULATOR MT6366_ID_RG_MAX #endif /* __LINUX_REGULATOR_MT6358_H */ diff --git a/include/linux/regulator/pca9450.h b/include/linux/regulator/pca9450.h index 71902f41c919..3c01c2bf84f5 100644 --- a/include/linux/regulator/pca9450.h +++ b/include/linux/regulator/pca9450.h @@ -226,4 +226,11 @@ enum { #define WDOG_B_CFG_COLD_LDO12 0x80 #define WDOG_B_CFG_COLD 0xC0 +/* PCA9450_REG_CONFIG2 bits */ +#define I2C_LT_MASK 0x03 +#define I2C_LT_FORCE_DISABLE 0x00 +#define I2C_LT_ON_STANDBY_RUN 0x01 +#define I2C_LT_ON_RUN 0x02 +#define I2C_LT_FORCE_ENABLE 0x03 + #endif /* __LINUX_REG_PCA9450_H__ */ diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index 5f8c063ddff4..df70eb1a671e 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -347,6 +347,7 @@ extern struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 ch * @max_message_size: function that returns the max message size for * a &spi_device; may be %NULL, so the default %SIZE_MAX will be used. * @io_mutex: mutex for physical bus access + * @add_lock: mutex to avoid adding devices to the same chipselect * @bus_lock_spinlock: spinlock for SPI bus locking * @bus_lock_mutex: mutex for exclusion of multiple callers * @bus_lock_flag: indicates that the SPI bus is locked for exclusive use @@ -361,6 +362,7 @@ extern struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 ch * @transfer: adds a message to the controller's transfer queue. * @cleanup: frees controller-specific state * @can_dma: determine whether this controller supports DMA + * @dma_map_dev: device which can be used for DMA mapping * @queued: whether this controller is providing an internal message queue * @kworker: pointer to thread struct for message pump * @pump_messages: work struct for scheduling work to the message pump diff --git a/include/linux/suspend.h b/include/linux/suspend.h index 8f7474f634b0..aa241e7bb0f6 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h @@ -543,22 +543,56 @@ static inline void unlock_system_sleep(void) {} #ifdef CONFIG_PM_SLEEP_DEBUG extern bool pm_print_times_enabled; extern bool pm_debug_messages_on; -extern __printf(2, 3) void __pm_pr_dbg(bool defer, const char *fmt, ...); +static inline int pm_dyn_debug_messages_on(void) +{ +#ifdef CONFIG_DYNAMIC_DEBUG + return 1; +#else + return 0; +#endif +} +#ifndef pr_fmt +#define pr_fmt(fmt) "PM: " fmt +#endif +#define __pm_pr_dbg(fmt, ...) \ + do { \ + if (pm_debug_messages_on) \ + printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__); \ + else if (pm_dyn_debug_messages_on()) \ + pr_debug(fmt, ##__VA_ARGS__); \ + } while (0) +#define __pm_deferred_pr_dbg(fmt, ...) \ + do { \ + if (pm_debug_messages_on) \ + printk_deferred(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__); \ + } while (0) #else #define pm_print_times_enabled (false) #define pm_debug_messages_on (false) #include -#define __pm_pr_dbg(defer, fmt, ...) \ - no_printk(KERN_DEBUG fmt, ##__VA_ARGS__) +#define __pm_pr_dbg(fmt, ...) \ + no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) +#define __pm_deferred_pr_dbg(fmt, ...) \ + no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) #endif +/** + * pm_pr_dbg - print pm sleep debug messages + * + * If pm_debug_messages_on is enabled, print message. + * If pm_debug_messages_on is disabled and CONFIG_DYNAMIC_DEBUG is enabled, + * print message only from instances explicitly enabled on dynamic debug's + * control. + * If pm_debug_messages_on is disabled and CONFIG_DYNAMIC_DEBUG is disabled, + * don't print message. + */ #define pm_pr_dbg(fmt, ...) \ - __pm_pr_dbg(false, fmt, ##__VA_ARGS__) + __pm_pr_dbg(fmt, ##__VA_ARGS__) #define pm_deferred_pr_dbg(fmt, ...) \ - __pm_pr_dbg(true, fmt, ##__VA_ARGS__) + __pm_deferred_pr_dbg(fmt, ##__VA_ARGS__) #ifdef CONFIG_PM_AUTOSLEEP diff --git a/include/soc/fsl/caam-blob.h b/include/soc/fsl/caam-blob.h new file mode 100644 index 000000000000..937cac52f36d --- /dev/null +++ b/include/soc/fsl/caam-blob.h @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 Pengutronix, Ahmad Fatoum + */ + +#ifndef __CAAM_BLOB_GEN +#define __CAAM_BLOB_GEN + +#include +#include + +#define CAAM_BLOB_KEYMOD_LENGTH 16 +#define CAAM_BLOB_OVERHEAD (32 + 16) +#define CAAM_BLOB_MAX_LEN 4096 + +struct caam_blob_priv; + +/** + * struct caam_blob_info - information for CAAM blobbing + * @input: pointer to input buffer (must be DMAable) + * @input_len: length of @input buffer in bytes. + * @output: pointer to output buffer (must be DMAable) + * @output_len: length of @output buffer in bytes. + * @key_mod: key modifier + * @key_mod_len: length of @key_mod in bytes. + * May not exceed %CAAM_BLOB_KEYMOD_LENGTH + */ +struct caam_blob_info { + void *input; + size_t input_len; + + void *output; + size_t output_len; + + const void *key_mod; + size_t key_mod_len; +}; + +/** + * caam_blob_gen_init - initialize blob generation + * Return: pointer to new &struct caam_blob_priv instance on success + * and ``ERR_PTR(-ENODEV)`` if CAAM has no hardware blobbing support + * or no job ring could be allocated. + */ +struct caam_blob_priv *caam_blob_gen_init(void); + +/** + * caam_blob_gen_exit - free blob generation resources + * @priv: instance returned by caam_blob_gen_init() + */ +void caam_blob_gen_exit(struct caam_blob_priv *priv); + +/** + * caam_process_blob - encapsulate or decapsulate blob + * @priv: instance returned by caam_blob_gen_init() + * @info: pointer to blobbing info describing key, blob and + * key modifier buffers. + * @encap: true for encapsulation, false for decapsulation + * + * Return: %0 and sets ``info->output_len`` on success and a negative + * error code otherwise. + */ +int caam_process_blob(struct caam_blob_priv *priv, + struct caam_blob_info *info, bool encap); + +/** + * caam_encap_blob - encapsulate blob + * @priv: instance returned by caam_blob_gen_init() + * @info: pointer to blobbing info describing input key, + * output blob and key modifier buffers. + * + * Return: %0 and sets ``info->output_len`` on success and + * a negative error code otherwise. + */ +static inline int caam_encap_blob(struct caam_blob_priv *priv, + struct caam_blob_info *info) +{ + if (info->output_len < info->input_len + CAAM_BLOB_OVERHEAD) + return -EINVAL; + + return caam_process_blob(priv, info, true); +} + +/** + * caam_decap_blob - decapsulate blob + * @priv: instance returned by caam_blob_gen_init() + * @info: pointer to blobbing info describing output key, + * input blob and key modifier buffers. + * + * Return: %0 and sets ``info->output_len`` on success and + * a negative error code otherwise. + */ +static inline int caam_decap_blob(struct caam_blob_priv *priv, + struct caam_blob_info *info) +{ + if (info->input_len < CAAM_BLOB_OVERHEAD || + info->output_len < info->input_len - CAAM_BLOB_OVERHEAD) + return -EINVAL; + + return caam_process_blob(priv, info, false); +} + +#endif diff --git a/include/soc/rockchip/pm_domains.h b/include/soc/rockchip/pm_domains.h new file mode 100644 index 000000000000..7dbd941fc937 --- /dev/null +++ b/include/soc/rockchip/pm_domains.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2022, The Chromium OS Authors. All rights reserved. + */ + +#ifndef __SOC_ROCKCHIP_PM_DOMAINS_H__ +#define __SOC_ROCKCHIP_PM_DOMAINS_H__ + +#ifdef CONFIG_ROCKCHIP_PM_DOMAINS + +int rockchip_pmu_block(void); +void rockchip_pmu_unblock(void); + +#else /* CONFIG_ROCKCHIP_PM_DOMAINS */ + +static inline int rockchip_pmu_block(void) +{ + return 0; +} + +static inline void rockchip_pmu_unblock(void) { } + +#endif /* CONFIG_ROCKCHIP_PM_DOMAINS */ + +#endif /* __SOC_ROCKCHIP_PM_DOMAINS_H__ */ diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h index ee3127461ee0..ef4257ab3026 100644 --- a/include/uapi/linux/input.h +++ b/include/uapi/linux/input.h @@ -271,6 +271,7 @@ struct input_mask { #define BUS_RMI 0x1D #define BUS_CEC 0x1E #define BUS_INTEL_ISHTP 0x1F +#define BUS_AMD_SFH 0x20 /* * MT_TOOL types diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index b3d952067f59..23df4e0e8ace 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -21,8 +21,14 @@ struct landlock_ruleset_attr { /** * @handled_access_fs: Bitmask of actions (cf. `Filesystem flags`_) * that is handled by this ruleset and should then be forbidden if no - * rule explicitly allow them. This is needed for backward - * compatibility reasons. + * rule explicitly allow them: it is a deny-by-default list that should + * contain as much Landlock access rights as possible. Indeed, all + * Landlock filesystem access rights that are not part of + * handled_access_fs are allowed. This is needed for backward + * compatibility reasons. One exception is the + * LANDLOCK_ACCESS_FS_REFER access right, which is always implicitly + * handled, but must still be explicitly handled to add new rules with + * this access right. */ __u64 handled_access_fs; }; @@ -33,7 +39,9 @@ struct landlock_ruleset_attr { * - %LANDLOCK_CREATE_RULESET_VERSION: Get the highest supported Landlock ABI * version. */ +/* clang-format off */ #define LANDLOCK_CREATE_RULESET_VERSION (1U << 0) +/* clang-format on */ /** * enum landlock_rule_type - Landlock rule type @@ -60,8 +68,9 @@ struct landlock_path_beneath_attr { */ __u64 allowed_access; /** - * @parent_fd: File descriptor, open with ``O_PATH``, which identifies - * the parent directory of a file hierarchy, or just a file. + * @parent_fd: File descriptor, preferably opened with ``O_PATH``, + * which identifies the parent directory of a file hierarchy, or just a + * file. */ __s32 parent_fd; /* @@ -109,6 +118,22 @@ struct landlock_path_beneath_attr { * - %LANDLOCK_ACCESS_FS_MAKE_FIFO: Create (or rename or link) a named pipe. * - %LANDLOCK_ACCESS_FS_MAKE_BLOCK: Create (or rename or link) a block device. * - %LANDLOCK_ACCESS_FS_MAKE_SYM: Create (or rename or link) a symbolic link. + * - %LANDLOCK_ACCESS_FS_REFER: Link or rename a file from or to a different + * directory (i.e. reparent a file hierarchy). This access right is + * available since the second version of the Landlock ABI. This is also the + * only access right which is always considered handled by any ruleset in + * such a way that reparenting a file hierarchy is always denied by default. + * To avoid privilege escalation, it is not enough to add a rule with this + * access right. When linking or renaming a file, the destination directory + * hierarchy must also always have the same or a superset of restrictions of + * the source hierarchy. If it is not the case, or if the domain doesn't + * handle this access right, such actions are denied by default with errno + * set to EXDEV. Linking also requires a LANDLOCK_ACCESS_FS_MAKE_* access + * right on the destination directory, and renaming also requires a + * LANDLOCK_ACCESS_FS_REMOVE_* access right on the source's (file or + * directory) parent. Otherwise, such actions are denied with errno set to + * EACCES. The EACCES errno prevails over EXDEV to let user space + * efficiently deal with an unrecoverable error. * * .. warning:: * @@ -120,6 +145,7 @@ struct landlock_path_beneath_attr { * :manpage:`access(2)`. * Future Landlock evolutions will enable to restrict them. */ +/* clang-format off */ #define LANDLOCK_ACCESS_FS_EXECUTE (1ULL << 0) #define LANDLOCK_ACCESS_FS_WRITE_FILE (1ULL << 1) #define LANDLOCK_ACCESS_FS_READ_FILE (1ULL << 2) @@ -133,5 +159,7 @@ struct landlock_path_beneath_attr { #define LANDLOCK_ACCESS_FS_MAKE_FIFO (1ULL << 10) #define LANDLOCK_ACCESS_FS_MAKE_BLOCK (1ULL << 11) #define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12) +#define LANDLOCK_ACCESS_FS_REFER (1ULL << 13) +/* clang-format on */ #endif /* _UAPI_LINUX_LANDLOCK_H */ diff --git a/include/uapi/linux/spi/spi.h b/include/uapi/linux/spi/spi.h index 236a85f08ded..9d5f58059703 100644 --- a/include/uapi/linux/spi/spi.h +++ b/include/uapi/linux/spi/spi.h @@ -27,6 +27,7 @@ #define SPI_TX_OCTAL _BITUL(13) /* transmit with 8 wires */ #define SPI_RX_OCTAL _BITUL(14) /* receive with 8 wires */ #define SPI_3WIRE_HIZ _BITUL(15) /* high impedance turnaround */ +#define SPI_RX_CPHA_FLIP _BITUL(16) /* flip CPHA on Rx only xfer */ /* * All the bits defined above should be covered by SPI_MODE_USER_MASK. @@ -36,6 +37,6 @@ * These bits must not overlap. A static assert check should make sure of that. * If adding extra bits, make sure to increase the bit index below as well. */ -#define SPI_MODE_USER_MASK (_BITUL(16) - 1) +#define SPI_MODE_USER_MASK (_BITUL(17) - 1) #endif /* _UAPI_SPI_H */ diff --git a/kernel/power/Makefile b/kernel/power/Makefile index 97705757f9c6..3e84020e7606 100644 --- a/kernel/power/Makefile +++ b/kernel/power/Makefile @@ -1,6 +1,10 @@ # SPDX-License-Identifier: GPL-2.0 -ccflags-$(CONFIG_PM_DEBUG) := -DDEBUG +ifeq ($(CONFIG_DYNAMIC_DEBUG), y) +CFLAGS_swap.o := -DDEBUG +CFLAGS_snapshot.o := -DDEBUG +CFLAGS_energy_model.o := -DDEBUG +endif KASAN_SANITIZE_snapshot.o := n diff --git a/kernel/power/energy_model.c b/kernel/power/energy_model.c index 0153b0ca7b23..6c373f2960e7 100644 --- a/kernel/power/energy_model.c +++ b/kernel/power/energy_model.c @@ -54,28 +54,15 @@ static int em_debug_cpus_show(struct seq_file *s, void *unused) } DEFINE_SHOW_ATTRIBUTE(em_debug_cpus); -static int em_debug_units_show(struct seq_file *s, void *unused) +static int em_debug_flags_show(struct seq_file *s, void *unused) { struct em_perf_domain *pd = s->private; - char *units = (pd->flags & EM_PERF_DOMAIN_MILLIWATTS) ? - "milliWatts" : "bogoWatts"; - seq_printf(s, "%s\n", units); + seq_printf(s, "%#lx\n", pd->flags); return 0; } -DEFINE_SHOW_ATTRIBUTE(em_debug_units); - -static int em_debug_skip_inefficiencies_show(struct seq_file *s, void *unused) -{ - struct em_perf_domain *pd = s->private; - int enabled = (pd->flags & EM_PERF_DOMAIN_SKIP_INEFFICIENCIES) ? 1 : 0; - - seq_printf(s, "%d\n", enabled); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(em_debug_skip_inefficiencies); +DEFINE_SHOW_ATTRIBUTE(em_debug_flags); static void em_debug_create_pd(struct device *dev) { @@ -89,9 +76,8 @@ static void em_debug_create_pd(struct device *dev) debugfs_create_file("cpus", 0444, d, dev->em_pd->cpus, &em_debug_cpus_fops); - debugfs_create_file("units", 0444, d, dev->em_pd, &em_debug_units_fops); - debugfs_create_file("skip-inefficiencies", 0444, d, dev->em_pd, - &em_debug_skip_inefficiencies_fops); + debugfs_create_file("flags", 0444, d, dev->em_pd, + &em_debug_flags_fops); /* Create a sub-directory for each performance state */ for (i = 0; i < dev->em_pd->nr_perf_states; i++) @@ -121,7 +107,8 @@ static void em_debug_remove_pd(struct device *dev) {} #endif static int em_create_perf_table(struct device *dev, struct em_perf_domain *pd, - int nr_states, struct em_data_callback *cb) + int nr_states, struct em_data_callback *cb, + unsigned long flags) { unsigned long power, freq, prev_freq = 0, prev_cost = ULONG_MAX; struct em_perf_state *table; @@ -139,7 +126,7 @@ static int em_create_perf_table(struct device *dev, struct em_perf_domain *pd, * lowest performance state of 'dev' above 'freq' and updates * 'power' and 'freq' accordingly. */ - ret = cb->active_power(&power, &freq, dev); + ret = cb->active_power(dev, &power, &freq); if (ret) { dev_err(dev, "EM: invalid perf. state: %d\n", ret); @@ -173,10 +160,22 @@ static int em_create_perf_table(struct device *dev, struct em_perf_domain *pd, /* Compute the cost of each performance state. */ fmax = (u64) table[nr_states - 1].frequency; for (i = nr_states - 1; i >= 0; i--) { - unsigned long power_res = em_scale_power(table[i].power); + unsigned long power_res, cost; + + if (flags & EM_PERF_DOMAIN_ARTIFICIAL) { + ret = cb->get_cost(dev, table[i].frequency, &cost); + if (ret || !cost || cost > EM_MAX_POWER) { + dev_err(dev, "EM: invalid cost %lu %d\n", + cost, ret); + goto free_ps_table; + } + } else { + power_res = em_scale_power(table[i].power); + cost = div64_u64(fmax * power_res, table[i].frequency); + } + + table[i].cost = cost; - table[i].cost = div64_u64(fmax * power_res, - table[i].frequency); if (table[i].cost >= prev_cost) { table[i].flags = EM_PERF_STATE_INEFFICIENT; dev_dbg(dev, "EM: OPP:%lu is inefficient\n", @@ -197,7 +196,8 @@ free_ps_table: } static int em_create_pd(struct device *dev, int nr_states, - struct em_data_callback *cb, cpumask_t *cpus) + struct em_data_callback *cb, cpumask_t *cpus, + unsigned long flags) { struct em_perf_domain *pd; struct device *cpu_dev; @@ -215,7 +215,7 @@ static int em_create_pd(struct device *dev, int nr_states, return -ENOMEM; } - ret = em_create_perf_table(dev, pd, nr_states, cb); + ret = em_create_perf_table(dev, pd, nr_states, cb, flags); if (ret) { kfree(pd); return ret; @@ -259,6 +259,8 @@ static void em_cpufreq_update_efficiencies(struct device *dev) found++; } + cpufreq_cpu_put(policy); + if (!found) return; @@ -332,6 +334,7 @@ int em_dev_register_perf_domain(struct device *dev, unsigned int nr_states, bool milliwatts) { unsigned long cap, prev_cap = 0; + unsigned long flags = 0; int cpu, ret; if (!dev || !nr_states || !cb) @@ -378,12 +381,16 @@ int em_dev_register_perf_domain(struct device *dev, unsigned int nr_states, } } - ret = em_create_pd(dev, nr_states, cb, cpus); + if (milliwatts) + flags |= EM_PERF_DOMAIN_MILLIWATTS; + else if (cb->get_cost) + flags |= EM_PERF_DOMAIN_ARTIFICIAL; + + ret = em_create_pd(dev, nr_states, cb, cpus, flags); if (ret) goto unlock; - if (milliwatts) - dev->em_pd->flags |= EM_PERF_DOMAIN_MILLIWATTS; + dev->em_pd->flags |= flags; em_cpufreq_update_efficiencies(dev); diff --git a/kernel/power/main.c b/kernel/power/main.c index 7e646079fbeb..5242bf2ee469 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c @@ -545,35 +545,6 @@ static int __init pm_debug_messages_setup(char *str) } __setup("pm_debug_messages", pm_debug_messages_setup); -/** - * __pm_pr_dbg - Print a suspend debug message to the kernel log. - * @defer: Whether or not to use printk_deferred() to print the message. - * @fmt: Message format. - * - * The message will be emitted if enabled through the pm_debug_messages - * sysfs attribute. - */ -void __pm_pr_dbg(bool defer, const char *fmt, ...) -{ - struct va_format vaf; - va_list args; - - if (!pm_debug_messages_on) - return; - - va_start(args, fmt); - - vaf.fmt = fmt; - vaf.va = &args; - - if (defer) - printk_deferred(KERN_DEBUG "PM: %pV", &vaf); - else - printk(KERN_DEBUG "PM: %pV", &vaf); - - va_end(args); -} - #else /* !CONFIG_PM_SLEEP_DEBUG */ static inline void pm_print_times_init(void) {} #endif /* CONFIG_PM_SLEEP_DEBUG */ diff --git a/kernel/power/process.c b/kernel/power/process.c index a01b87f35a99..accc1e48f76f 100644 --- a/kernel/power/process.c +++ b/kernel/power/process.c @@ -6,9 +6,6 @@ * Originally from swsusp. */ - -#undef DEBUG - #include #include #include diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c index 330d49937692..2a406753af90 100644 --- a/kernel/power/snapshot.c +++ b/kernel/power/snapshot.c @@ -326,7 +326,7 @@ static void *chain_alloc(struct chain_allocator *ca, unsigned int size) return ret; } -/** +/* * Data types related to memory bitmaps. * * Memory bitmap is a structure consisting of many linked lists of @@ -427,6 +427,10 @@ struct memory_bitmap { /** * alloc_rtree_node - Allocate a new node and add it to the radix tree. + * @gfp_mask: GFP mask for the allocation. + * @safe_needed: Get pages not used before hibernation (restore only) + * @ca: Pointer to a linked list of pages ("a chain") to allocate from + * @list: Radix Tree node to add. * * This function is used to allocate inner nodes as well as the * leave nodes of the radix tree. It also adds the node to the @@ -902,7 +906,7 @@ static bool rtree_next_node(struct memory_bitmap *bm) } /** - * memory_bm_rtree_next_pfn - Find the next set bit in a memory bitmap. + * memory_bm_next_pfn - Find the next set bit in a memory bitmap. * @bm: Memory bitmap. * * Starting from the last returned position this function searches for the next @@ -1937,7 +1941,7 @@ static inline int get_highmem_buffer(int safe_needed) } /** - * alloc_highmem_image_pages - Allocate some highmem pages for the image. + * alloc_highmem_pages - Allocate some highmem pages for the image. * * Try to allocate as many pages as needed, but if the number of free highmem * pages is less than that, allocate them all. @@ -2224,7 +2228,7 @@ static int check_header(struct swsusp_info *info) } /** - * load header - Check the image header and copy the data from it. + * load_header - Check the image header and copy the data from it. */ static int load_header(struct swsusp_info *info) { diff --git a/lib/Kconfig b/lib/Kconfig index 087e06b4cdfd..6a843639814f 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -737,3 +737,6 @@ config PLDMFW config ASN1_ENCODER tristate + +config POLYNOMIAL + tristate diff --git a/lib/Makefile b/lib/Makefile index 6b9ffc1bd1ee..89fcae891361 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -263,6 +263,8 @@ obj-$(CONFIG_MEMREGION) += memregion.o obj-$(CONFIG_STMP_DEVICE) += stmp_device.o obj-$(CONFIG_IRQ_POLL) += irq_poll.o +obj-$(CONFIG_POLYNOMIAL) += polynomial.o + # stackdepot.c should not be instrumented or call instrumented functions. # Prevent the compiler from calling builtins like memcmp() or bcmp() from this # file. diff --git a/lib/polynomial.c b/lib/polynomial.c new file mode 100644 index 000000000000..66d383445fec --- /dev/null +++ b/lib/polynomial.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Generic polynomial calculation using integer coefficients. + * + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Authors: + * Maxim Kaurkin + * Serge Semin + * + */ + +#include +#include +#include + +/* + * Originally this was part of drivers/hwmon/bt1-pvt.c. + * There the following conversion is used and should serve as an example here: + * + * The original translation formulae of the temperature (in degrees of Celsius) + * to PVT data and vice-versa are following: + * + * N = 1.8322e-8*(T^4) + 2.343e-5*(T^3) + 8.7018e-3*(T^2) + 3.9269*(T^1) + + * 1.7204e2 + * T = -1.6743e-11*(N^4) + 8.1542e-8*(N^3) + -1.8201e-4*(N^2) + + * 3.1020e-1*(N^1) - 4.838e1 + * + * where T = [-48.380, 147.438]C and N = [0, 1023]. + * + * They must be accordingly altered to be suitable for the integer arithmetics. + * The technique is called 'factor redistribution', which just makes sure the + * multiplications and divisions are made so to have a result of the operations + * within the integer numbers limit. In addition we need to translate the + * formulae to accept millidegrees of Celsius. Here what they look like after + * the alterations: + * + * N = (18322e-20*(T^4) + 2343e-13*(T^3) + 87018e-9*(T^2) + 39269e-3*T + + * 17204e2) / 1e4 + * T = -16743e-12*(D^4) + 81542e-9*(D^3) - 182010e-6*(D^2) + 310200e-3*D - + * 48380 + * where T = [-48380, 147438] mC and N = [0, 1023]. + * + * static const struct polynomial poly_temp_to_N = { + * .total_divider = 10000, + * .terms = { + * {4, 18322, 10000, 10000}, + * {3, 2343, 10000, 10}, + * {2, 87018, 10000, 10}, + * {1, 39269, 1000, 1}, + * {0, 1720400, 1, 1} + * } + * }; + * + * static const struct polynomial poly_N_to_temp = { + * .total_divider = 1, + * .terms = { + * {4, -16743, 1000, 1}, + * {3, 81542, 1000, 1}, + * {2, -182010, 1000, 1}, + * {1, 310200, 1000, 1}, + * {0, -48380, 1, 1} + * } + * }; + */ + +/** + * polynomial_calc - calculate a polynomial using integer arithmetic + * + * @poly: pointer to the descriptor of the polynomial + * @data: input value of the polynimal + * + * Calculate the result of a polynomial using only integer arithmetic. For + * this to work without too much loss of precision the coefficients has to + * be altered. This is called factor redistribution. + * + * Returns the result of the polynomial calculation. + */ +long polynomial_calc(const struct polynomial *poly, long data) +{ + const struct polynomial_term *term = poly->terms; + long total_divider = poly->total_divider ?: 1; + long tmp, ret = 0; + int deg; + + /* + * Here is the polynomial calculation function, which performs the + * redistributed terms calculations. It's pretty straightforward. + * We walk over each degree term up to the free one, and perform + * the redistributed multiplication of the term coefficient, its + * divider (as for the rationale fraction representation), data + * power and the rational fraction divider leftover. Then all of + * this is collected in a total sum variable, which value is + * normalized by the total divider before being returned. + */ + do { + tmp = term->coef; + for (deg = 0; deg < term->deg; ++deg) + tmp = mult_frac(tmp, data, term->divider); + ret += tmp / term->divider_leftover; + } while ((term++)->deg); + + return ret / total_divider; +} +EXPORT_SYMBOL_GPL(polynomial_calc); + +MODULE_DESCRIPTION("Generic polynomial calculations"); +MODULE_LICENSE("GPL"); diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c index 8859fc193542..3e404e51ec64 100644 --- a/samples/landlock/sandboxer.c +++ b/samples/landlock/sandboxer.c @@ -22,9 +22,9 @@ #include #ifndef landlock_create_ruleset -static inline int landlock_create_ruleset( - const struct landlock_ruleset_attr *const attr, - const size_t size, const __u32 flags) +static inline int +landlock_create_ruleset(const struct landlock_ruleset_attr *const attr, + const size_t size, const __u32 flags) { return syscall(__NR_landlock_create_ruleset, attr, size, flags); } @@ -32,17 +32,18 @@ static inline int landlock_create_ruleset( #ifndef landlock_add_rule static inline int landlock_add_rule(const int ruleset_fd, - const enum landlock_rule_type rule_type, - const void *const rule_attr, const __u32 flags) + const enum landlock_rule_type rule_type, + const void *const rule_attr, + const __u32 flags) { - return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, - rule_attr, flags); + return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr, + flags); } #endif #ifndef landlock_restrict_self static inline int landlock_restrict_self(const int ruleset_fd, - const __u32 flags) + const __u32 flags) { return syscall(__NR_landlock_restrict_self, ruleset_fd, flags); } @@ -70,14 +71,17 @@ static int parse_path(char *env_path, const char ***const path_list) return num_paths; } +/* clang-format off */ + #define ACCESS_FILE ( \ LANDLOCK_ACCESS_FS_EXECUTE | \ LANDLOCK_ACCESS_FS_WRITE_FILE | \ LANDLOCK_ACCESS_FS_READ_FILE) -static int populate_ruleset( - const char *const env_var, const int ruleset_fd, - const __u64 allowed_access) +/* clang-format on */ + +static int populate_ruleset(const char *const env_var, const int ruleset_fd, + const __u64 allowed_access) { int num_paths, i, ret = 1; char *env_path_name; @@ -107,12 +111,10 @@ static int populate_ruleset( for (i = 0; i < num_paths; i++) { struct stat statbuf; - path_beneath.parent_fd = open(path_list[i], O_PATH | - O_CLOEXEC); + path_beneath.parent_fd = open(path_list[i], O_PATH | O_CLOEXEC); if (path_beneath.parent_fd < 0) { fprintf(stderr, "Failed to open \"%s\": %s\n", - path_list[i], - strerror(errno)); + path_list[i], strerror(errno)); goto out_free_name; } if (fstat(path_beneath.parent_fd, &statbuf)) { @@ -123,9 +125,10 @@ static int populate_ruleset( if (!S_ISDIR(statbuf.st_mode)) path_beneath.allowed_access &= ACCESS_FILE; if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, - &path_beneath, 0)) { - fprintf(stderr, "Failed to update the ruleset with \"%s\": %s\n", - path_list[i], strerror(errno)); + &path_beneath, 0)) { + fprintf(stderr, + "Failed to update the ruleset with \"%s\": %s\n", + path_list[i], strerror(errno)); close(path_beneath.parent_fd); goto out_free_name; } @@ -139,6 +142,8 @@ out_free_name: return ret; } +/* clang-format off */ + #define ACCESS_FS_ROUGHLY_READ ( \ LANDLOCK_ACCESS_FS_EXECUTE | \ LANDLOCK_ACCESS_FS_READ_FILE | \ @@ -154,64 +159,89 @@ out_free_name: LANDLOCK_ACCESS_FS_MAKE_SOCK | \ LANDLOCK_ACCESS_FS_MAKE_FIFO | \ LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ - LANDLOCK_ACCESS_FS_MAKE_SYM) + LANDLOCK_ACCESS_FS_MAKE_SYM | \ + LANDLOCK_ACCESS_FS_REFER) + +#define ACCESS_ABI_2 ( \ + LANDLOCK_ACCESS_FS_REFER) + +/* clang-format on */ int main(const int argc, char *const argv[], char *const *const envp) { const char *cmd_path; char *const *cmd_argv; - int ruleset_fd; + int ruleset_fd, abi; + __u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ, + access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE; struct landlock_ruleset_attr ruleset_attr = { - .handled_access_fs = ACCESS_FS_ROUGHLY_READ | - ACCESS_FS_ROUGHLY_WRITE, + .handled_access_fs = access_fs_rw, }; if (argc < 2) { - fprintf(stderr, "usage: %s=\"...\" %s=\"...\" %s [args]...\n\n", - ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]); - fprintf(stderr, "Launch a command in a restricted environment.\n\n"); + fprintf(stderr, + "usage: %s=\"...\" %s=\"...\" %s [args]...\n\n", + ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]); + fprintf(stderr, + "Launch a command in a restricted environment.\n\n"); fprintf(stderr, "Environment variables containing paths, " "each separated by a colon:\n"); - fprintf(stderr, "* %s: list of paths allowed to be used in a read-only way.\n", - ENV_FS_RO_NAME); - fprintf(stderr, "* %s: list of paths allowed to be used in a read-write way.\n", - ENV_FS_RW_NAME); - fprintf(stderr, "\nexample:\n" - "%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" " - "%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" " - "%s bash -i\n", - ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]); + fprintf(stderr, + "* %s: list of paths allowed to be used in a read-only way.\n", + ENV_FS_RO_NAME); + fprintf(stderr, + "* %s: list of paths allowed to be used in a read-write way.\n", + ENV_FS_RW_NAME); + fprintf(stderr, + "\nexample:\n" + "%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" " + "%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" " + "%s bash -i\n", + ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]); return 1; } - ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); - if (ruleset_fd < 0) { + abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); + if (abi < 0) { const int err = errno; - perror("Failed to create a ruleset"); + perror("Failed to check Landlock compatibility"); switch (err) { case ENOSYS: - fprintf(stderr, "Hint: Landlock is not supported by the current kernel. " - "To support it, build the kernel with " - "CONFIG_SECURITY_LANDLOCK=y and prepend " - "\"landlock,\" to the content of CONFIG_LSM.\n"); + fprintf(stderr, + "Hint: Landlock is not supported by the current kernel. " + "To support it, build the kernel with " + "CONFIG_SECURITY_LANDLOCK=y and prepend " + "\"landlock,\" to the content of CONFIG_LSM.\n"); break; case EOPNOTSUPP: - fprintf(stderr, "Hint: Landlock is currently disabled. " - "It can be enabled in the kernel configuration by " - "prepending \"landlock,\" to the content of CONFIG_LSM, " - "or at boot time by setting the same content to the " - "\"lsm\" kernel parameter.\n"); + fprintf(stderr, + "Hint: Landlock is currently disabled. " + "It can be enabled in the kernel configuration by " + "prepending \"landlock,\" to the content of CONFIG_LSM, " + "or at boot time by setting the same content to the " + "\"lsm\" kernel parameter.\n"); break; } return 1; } - if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, - ACCESS_FS_ROUGHLY_READ)) { + /* Best-effort security. */ + if (abi < 2) { + ruleset_attr.handled_access_fs &= ~ACCESS_ABI_2; + access_fs_ro &= ~ACCESS_ABI_2; + access_fs_rw &= ~ACCESS_ABI_2; + } + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + if (ruleset_fd < 0) { + perror("Failed to create a ruleset"); + return 1; + } + if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) { goto err_close_ruleset; } - if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, - ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE)) { + if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) { goto err_close_ruleset; } if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { @@ -228,7 +258,7 @@ int main(const int argc, char *const argv[], char *const *const envp) cmd_argv = argv + 1; execvpe(cmd_path, cmd_argv, envp); fprintf(stderr, "Failed to execute \"%s\": %s\n", cmd_path, - strerror(errno)); + strerror(errno)); fprintf(stderr, "Hint: access to the binary, the interpreter or " "shared libraries may be denied.\n"); return 1; diff --git a/scripts/check-blacklist-hashes.awk b/scripts/check-blacklist-hashes.awk new file mode 100755 index 000000000000..107c1d3204d4 --- /dev/null +++ b/scripts/check-blacklist-hashes.awk @@ -0,0 +1,37 @@ +#!/usr/bin/awk -f +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright © 2020, Microsoft Corporation. All rights reserved. +# +# Author: Mickaël Salaün +# +# Check that a CONFIG_SYSTEM_BLACKLIST_HASH_LIST file contains a valid array of +# hash strings. Such string must start with a prefix ("tbs" or "bin"), then a +# colon (":"), and finally an even number of hexadecimal lowercase characters +# (up to 128). + +BEGIN { + RS = "," +} +{ + if (!match($0, "^[ \t\n\r]*\"([^\"]*)\"[ \t\n\r]*$", part1)) { + print "Not a string (item " NR "):", $0; + exit 1; + } + if (!match(part1[1], "^(tbs|bin):(.*)$", part2)) { + print "Unknown prefix (item " NR "):", part1[1]; + exit 1; + } + if (!match(part2[2], "^([0-9a-f]+)$", part3)) { + print "Not a lowercase hexadecimal string (item " NR "):", part2[2]; + exit 1; + } + if (length(part3[1]) > 128) { + print "Hash string too long (item " NR "):", part3[1]; + exit 1; + } + if (length(part3[1]) % 2 == 1) { + print "Not an even number of hexadecimal characters (item " NR "):", part3[1]; + exit 1; + } +} diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 4f0eecb67dde..900bc540656a 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -354,13 +354,16 @@ static int apparmor_path_link(struct dentry *old_dentry, const struct path *new_ } static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_dentry, - const struct path *new_dir, struct dentry *new_dentry) + const struct path *new_dir, struct dentry *new_dentry, + const unsigned int flags) { struct aa_label *label; int error = 0; if (!path_mediated_fs(old_dentry)) return 0; + if ((flags & RENAME_EXCHANGE) && !path_mediated_fs(new_dentry)) + return 0; label = begin_current_label_crit_section(); if (!unconfined(label)) { @@ -374,10 +377,27 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d d_backing_inode(old_dentry)->i_mode }; - error = aa_path_perm(OP_RENAME_SRC, label, &old_path, 0, - MAY_READ | AA_MAY_GETATTR | MAY_WRITE | - AA_MAY_SETATTR | AA_MAY_DELETE, - &cond); + if (flags & RENAME_EXCHANGE) { + struct path_cond cond_exchange = { + i_uid_into_mnt(mnt_userns, d_backing_inode(new_dentry)), + d_backing_inode(new_dentry)->i_mode + }; + + error = aa_path_perm(OP_RENAME_SRC, label, &new_path, 0, + MAY_READ | AA_MAY_GETATTR | MAY_WRITE | + AA_MAY_SETATTR | AA_MAY_DELETE, + &cond_exchange); + if (!error) + error = aa_path_perm(OP_RENAME_DEST, label, &old_path, + 0, MAY_WRITE | AA_MAY_SETATTR | + AA_MAY_CREATE, &cond_exchange); + } + + if (!error) + error = aa_path_perm(OP_RENAME_SRC, label, &old_path, 0, + MAY_READ | AA_MAY_GETATTR | MAY_WRITE | + AA_MAY_SETATTR | AA_MAY_DELETE, + &cond); if (!error) error = aa_path_perm(OP_RENAME_DEST, label, &new_path, 0, MAY_WRITE | AA_MAY_SETATTR | diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c index c8c8a4a4e7a0..8a82a6c7f48a 100644 --- a/security/integrity/digsig.c +++ b/security/integrity/digsig.c @@ -75,7 +75,8 @@ int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, /* v1 API expect signature without xattr type */ return digsig_verify(keyring, sig + 1, siglen - 1, digest, digestlen); - case 2: + case 2: /* regular file data hash based signature */ + case 3: /* struct ima_file_id data based signature */ return asymmetric_verify(keyring, sig, siglen, digest, digestlen); } diff --git a/security/integrity/evm/evm.h b/security/integrity/evm/evm.h index 0d44f41d16f8..f8b8c5004fc7 100644 --- a/security/integrity/evm/evm.h +++ b/security/integrity/evm/evm.h @@ -38,9 +38,6 @@ extern int evm_initialized; extern int evm_hmac_attrs; -extern struct crypto_shash *hmac_tfm; -extern struct crypto_shash *hash_tfm; - /* List of EVM protected security xattrs */ extern struct list_head evm_config_xattrnames; diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c index 0450d79afdc8..a733aff02006 100644 --- a/security/integrity/evm/evm_crypto.c +++ b/security/integrity/evm/evm_crypto.c @@ -26,7 +26,7 @@ static unsigned char evmkey[MAX_KEY_SIZE]; static const int evmkey_len = MAX_KEY_SIZE; -struct crypto_shash *hmac_tfm; +static struct crypto_shash *hmac_tfm; static struct crypto_shash *evm_tfm[HASH_ALGO__LAST]; static DEFINE_MUTEX(mutex); diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 7d87772f0ce6..cc88f02c7562 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -436,7 +436,7 @@ static enum integrity_status evm_verify_current_integrity(struct dentry *dentry) struct inode *inode = d_backing_inode(dentry); if (!evm_key_loaded() || !S_ISREG(inode->i_mode) || evm_fixmode) - return 0; + return INTEGRITY_PASS; return evm_verify_hmac(dentry, NULL, NULL, 0, NULL); } diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index f3a9cc201c8c..7249f16257c7 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -69,10 +69,9 @@ choice hash, defined as 20 bytes, and a null terminated pathname, limited to 255 characters. The 'ima-ng' measurement list template permits both larger hash digests and longer - pathnames. + pathnames. The configured default template can be replaced + by specifying "ima_template=" on the boot command line. - config IMA_TEMPLATE - bool "ima" config IMA_NG_TEMPLATE bool "ima-ng (default)" config IMA_SIG_TEMPLATE @@ -82,7 +81,6 @@ endchoice config IMA_DEFAULT_TEMPLATE string depends on IMA - default "ima" if IMA_TEMPLATE default "ima-ng" if IMA_NG_TEMPLATE default "ima-sig" if IMA_SIG_TEMPLATE @@ -102,19 +100,19 @@ choice config IMA_DEFAULT_HASH_SHA256 bool "SHA256" - depends on CRYPTO_SHA256=y && !IMA_TEMPLATE + depends on CRYPTO_SHA256=y config IMA_DEFAULT_HASH_SHA512 bool "SHA512" - depends on CRYPTO_SHA512=y && !IMA_TEMPLATE + depends on CRYPTO_SHA512=y config IMA_DEFAULT_HASH_WP512 bool "WP512" - depends on CRYPTO_WP512=y && !IMA_TEMPLATE + depends on CRYPTO_WP512=y config IMA_DEFAULT_HASH_SM3 bool "SM3" - depends on CRYPTO_SM3=y && !IMA_TEMPLATE + depends on CRYPTO_SM3=y endchoice config IMA_DEFAULT_HASH diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index c6805af46211..c1e76282b5ee 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -14,6 +14,7 @@ #include #include #include +#include #include "ima.h" @@ -200,6 +201,32 @@ int ima_get_action(struct user_namespace *mnt_userns, struct inode *inode, allowed_algos); } +static int ima_get_verity_digest(struct integrity_iint_cache *iint, + struct ima_max_digest_data *hash) +{ + enum hash_algo verity_alg; + int ret; + + /* + * On failure, 'measure' policy rules will result in a file data + * hash containing 0's. + */ + ret = fsverity_get_digest(iint->inode, hash->digest, &verity_alg); + if (ret) + return ret; + + /* + * Unlike in the case of actually calculating the file hash, in + * the fsverity case regardless of the hash algorithm, return + * the verity digest to be included in the measurement list. A + * mismatch between the verity algorithm and the xattr signature + * algorithm, if one exists, will be detected later. + */ + hash->hdr.algo = verity_alg; + hash->hdr.length = hash_digest_size[verity_alg]; + return 0; +} + /* * ima_collect_measurement - collect file measurement * @@ -242,16 +269,30 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, */ i_version = inode_query_iversion(inode); hash.hdr.algo = algo; + hash.hdr.length = hash_digest_size[algo]; /* Initialize hash digest to 0's in case of failure */ memset(&hash.digest, 0, sizeof(hash.digest)); - if (buf) + if (iint->flags & IMA_VERITY_REQUIRED) { + result = ima_get_verity_digest(iint, &hash); + switch (result) { + case 0: + break; + case -ENODATA: + audit_cause = "no-verity-digest"; + break; + default: + audit_cause = "invalid-verity-digest"; + break; + } + } else if (buf) { result = ima_calc_buffer_hash(buf, size, &hash.hdr); - else + } else { result = ima_calc_file_hash(file, &hash.hdr); + } - if (result && result != -EBADF && result != -EINVAL) + if (result == -ENOMEM) goto out; length = sizeof(hash.hdr) + hash.hdr.length; diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 17232bbfb9f9..cdb84dccd24e 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -13,7 +13,9 @@ #include #include #include +#include #include +#include #include "ima.h" @@ -183,13 +185,18 @@ enum hash_algo ima_get_hash_algo(const struct evm_ima_xattr_data *xattr_value, return ima_hash_algo; switch (xattr_value->type) { + case IMA_VERITY_DIGSIG: + sig = (typeof(sig))xattr_value; + if (sig->version != 3 || xattr_len <= sizeof(*sig) || + sig->hash_algo >= HASH_ALGO__LAST) + return ima_hash_algo; + return sig->hash_algo; case EVM_IMA_XATTR_DIGSIG: sig = (typeof(sig))xattr_value; if (sig->version != 2 || xattr_len <= sizeof(*sig) || sig->hash_algo >= HASH_ALGO__LAST) return ima_hash_algo; return sig->hash_algo; - break; case IMA_XATTR_DIGEST_NG: /* first byte contains algorithm id */ ret = xattr_value->data[0]; @@ -225,6 +232,40 @@ int ima_read_xattr(struct dentry *dentry, return ret; } +/* + * calc_file_id_hash - calculate the hash of the ima_file_id struct data + * @type: xattr type [enum evm_ima_xattr_type] + * @algo: hash algorithm [enum hash_algo] + * @digest: pointer to the digest to be hashed + * @hash: (out) pointer to the hash + * + * IMA signature version 3 disambiguates the data that is signed by + * indirectly signing the hash of the ima_file_id structure data. + * + * Signing the ima_file_id struct is currently only supported for + * IMA_VERITY_DIGSIG type xattrs. + * + * Return 0 on success, error code otherwise. + */ +static int calc_file_id_hash(enum evm_ima_xattr_type type, + enum hash_algo algo, const u8 *digest, + struct ima_digest_data *hash) +{ + struct ima_file_id file_id = { + .hash_type = IMA_VERITY_DIGSIG, .hash_algorithm = algo}; + unsigned int unused = HASH_MAX_DIGESTSIZE - hash_digest_size[algo]; + + if (type != IMA_VERITY_DIGSIG) + return -EINVAL; + + memcpy(file_id.hash, digest, hash_digest_size[algo]); + + hash->algo = algo; + hash->length = hash_digest_size[algo]; + + return ima_calc_buffer_hash(&file_id, sizeof(file_id) - unused, hash); +} + /* * xattr_verify - verify xattr digest or signature * @@ -236,7 +277,10 @@ static int xattr_verify(enum ima_hooks func, struct integrity_iint_cache *iint, struct evm_ima_xattr_data *xattr_value, int xattr_len, enum integrity_status *status, const char **cause) { + struct ima_max_digest_data hash; + struct signature_v2_hdr *sig; int rc = -EINVAL, hash_start = 0; + int mask; switch (xattr_value->type) { case IMA_XATTR_DIGEST_NG: @@ -246,7 +290,10 @@ static int xattr_verify(enum ima_hooks func, struct integrity_iint_cache *iint, case IMA_XATTR_DIGEST: if (*status != INTEGRITY_PASS_IMMUTABLE) { if (iint->flags & IMA_DIGSIG_REQUIRED) { - *cause = "IMA-signature-required"; + if (iint->flags & IMA_VERITY_REQUIRED) + *cause = "verity-signature-required"; + else + *cause = "IMA-signature-required"; *status = INTEGRITY_FAIL; break; } @@ -274,6 +321,20 @@ static int xattr_verify(enum ima_hooks func, struct integrity_iint_cache *iint, break; case EVM_IMA_XATTR_DIGSIG: set_bit(IMA_DIGSIG, &iint->atomic_flags); + + mask = IMA_DIGSIG_REQUIRED | IMA_VERITY_REQUIRED; + if ((iint->flags & mask) == mask) { + *cause = "verity-signature-required"; + *status = INTEGRITY_FAIL; + break; + } + + sig = (typeof(sig))xattr_value; + if (sig->version >= 3) { + *cause = "invalid-signature-version"; + *status = INTEGRITY_FAIL; + break; + } rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA, (const char *)xattr_value, xattr_len, @@ -296,6 +357,44 @@ static int xattr_verify(enum ima_hooks func, struct integrity_iint_cache *iint, } else { *status = INTEGRITY_PASS; } + break; + case IMA_VERITY_DIGSIG: + set_bit(IMA_DIGSIG, &iint->atomic_flags); + + if (iint->flags & IMA_DIGSIG_REQUIRED) { + if (!(iint->flags & IMA_VERITY_REQUIRED)) { + *cause = "IMA-signature-required"; + *status = INTEGRITY_FAIL; + break; + } + } + + sig = (typeof(sig))xattr_value; + if (sig->version != 3) { + *cause = "invalid-signature-version"; + *status = INTEGRITY_FAIL; + break; + } + + rc = calc_file_id_hash(IMA_VERITY_DIGSIG, iint->ima_hash->algo, + iint->ima_hash->digest, &hash.hdr); + if (rc) { + *cause = "sigv3-hashing-error"; + *status = INTEGRITY_FAIL; + break; + } + + rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA, + (const char *)xattr_value, + xattr_len, hash.digest, + hash.hdr.length); + if (rc) { + *cause = "invalid-verity-signature"; + *status = INTEGRITY_FAIL; + } else { + *status = INTEGRITY_PASS; + } + break; default: *status = INTEGRITY_UNKNOWN; @@ -396,8 +495,15 @@ int ima_appraise_measurement(enum ima_hooks func, if (rc && rc != -ENODATA) goto out; - cause = iint->flags & IMA_DIGSIG_REQUIRED ? - "IMA-signature-required" : "missing-hash"; + if (iint->flags & IMA_DIGSIG_REQUIRED) { + if (iint->flags & IMA_VERITY_REQUIRED) + cause = "verity-signature-required"; + else + cause = "IMA-signature-required"; + } else { + cause = "missing-hash"; + } + status = INTEGRITY_NOLABEL; if (file->f_mode & FMODE_CREATED) iint->flags |= IMA_NEW_FILE; diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 3d3f8c5c502b..040b03ddc1c7 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -335,7 +335,7 @@ static int process_measurement(struct file *file, const struct cred *cred, hash_algo = ima_get_hash_algo(xattr_value, xattr_len); rc = ima_collect_measurement(iint, file, buf, size, hash_algo, modsig); - if (rc != 0 && rc != -EBADF && rc != -EINVAL) + if (rc == -ENOMEM) goto out_locked; if (!pathbuf) /* ima_rdwr_violation possibly pre-fetched */ @@ -432,7 +432,7 @@ int ima_file_mmap(struct file *file, unsigned long prot) int ima_file_mprotect(struct vm_area_struct *vma, unsigned long prot) { struct ima_template_desc *template = NULL; - struct file *file = vma->vm_file; + struct file *file; char filename[NAME_MAX]; char *pathbuf = NULL; const char *pathname = NULL; diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index eea6e92500b8..73917413365b 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -1023,6 +1023,7 @@ enum policy_opt { Opt_fowner_gt, Opt_fgroup_gt, Opt_uid_lt, Opt_euid_lt, Opt_gid_lt, Opt_egid_lt, Opt_fowner_lt, Opt_fgroup_lt, + Opt_digest_type, Opt_appraise_type, Opt_appraise_flag, Opt_appraise_algos, Opt_permit_directio, Opt_pcr, Opt_template, Opt_keyrings, Opt_label, Opt_err @@ -1065,6 +1066,7 @@ static const match_table_t policy_tokens = { {Opt_egid_lt, "egid<%s"}, {Opt_fowner_lt, "fowner<%s"}, {Opt_fgroup_lt, "fgroup<%s"}, + {Opt_digest_type, "digest_type=%s"}, {Opt_appraise_type, "appraise_type=%s"}, {Opt_appraise_flag, "appraise_flag=%s"}, {Opt_appraise_algos, "appraise_algos=%s"}, @@ -1172,6 +1174,21 @@ static void check_template_modsig(const struct ima_template_desc *template) #undef MSG } +/* + * Warn if the template does not contain the given field. + */ +static void check_template_field(const struct ima_template_desc *template, + const char *field, const char *msg) +{ + int i; + + for (i = 0; i < template->num_fields; i++) + if (!strcmp(template->fields[i]->field_id, field)) + return; + + pr_notice_once("%s", msg); +} + static bool ima_validate_rule(struct ima_rule_entry *entry) { /* Ensure that the action is set and is compatible with the flags */ @@ -1214,7 +1231,8 @@ static bool ima_validate_rule(struct ima_rule_entry *entry) IMA_INMASK | IMA_EUID | IMA_PCR | IMA_FSNAME | IMA_GID | IMA_EGID | IMA_FGROUP | IMA_DIGSIG_REQUIRED | - IMA_PERMIT_DIRECTIO | IMA_VALIDATE_ALGOS)) + IMA_PERMIT_DIRECTIO | IMA_VALIDATE_ALGOS | + IMA_VERITY_REQUIRED)) return false; break; @@ -1292,6 +1310,18 @@ static bool ima_validate_rule(struct ima_rule_entry *entry) !(entry->flags & IMA_MODSIG_ALLOWED)) return false; + /* + * Unlike for regular IMA 'appraise' policy rules where security.ima + * xattr may contain either a file hash or signature, the security.ima + * xattr for fsverity must contain a file signature (sigv3). Ensure + * that 'appraise' rules for fsverity require file signatures by + * checking the IMA_DIGSIG_REQUIRED flag is set. + */ + if (entry->action == APPRAISE && + (entry->flags & IMA_VERITY_REQUIRED) && + !(entry->flags & IMA_DIGSIG_REQUIRED)) + return false; + return true; } @@ -1707,17 +1737,40 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) LSM_SUBJ_TYPE, AUDIT_SUBJ_TYPE); break; - case Opt_appraise_type: - ima_log_string(ab, "appraise_type", args[0].from); - if ((strcmp(args[0].from, "imasig")) == 0) - entry->flags |= IMA_DIGSIG_REQUIRED; - else if (IS_ENABLED(CONFIG_IMA_APPRAISE_MODSIG) && - strcmp(args[0].from, "imasig|modsig") == 0) - entry->flags |= IMA_DIGSIG_REQUIRED | - IMA_MODSIG_ALLOWED; + case Opt_digest_type: + ima_log_string(ab, "digest_type", args[0].from); + if (entry->flags & IMA_DIGSIG_REQUIRED) + result = -EINVAL; + else if ((strcmp(args[0].from, "verity")) == 0) + entry->flags |= IMA_VERITY_REQUIRED; else result = -EINVAL; break; + case Opt_appraise_type: + ima_log_string(ab, "appraise_type", args[0].from); + + if ((strcmp(args[0].from, "imasig")) == 0) { + if (entry->flags & IMA_VERITY_REQUIRED) + result = -EINVAL; + else + entry->flags |= IMA_DIGSIG_REQUIRED; + } else if (strcmp(args[0].from, "sigv3") == 0) { + /* Only fsverity supports sigv3 for now */ + if (entry->flags & IMA_VERITY_REQUIRED) + entry->flags |= IMA_DIGSIG_REQUIRED; + else + result = -EINVAL; + } else if (IS_ENABLED(CONFIG_IMA_APPRAISE_MODSIG) && + strcmp(args[0].from, "imasig|modsig") == 0) { + if (entry->flags & IMA_VERITY_REQUIRED) + result = -EINVAL; + else + entry->flags |= IMA_DIGSIG_REQUIRED | + IMA_MODSIG_ALLOWED; + } else { + result = -EINVAL; + } + break; case Opt_appraise_flag: ima_log_string(ab, "appraise_flag", args[0].from); if (IS_ENABLED(CONFIG_IMA_APPRAISE_MODSIG) && @@ -1797,6 +1850,15 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) check_template_modsig(template_desc); } + /* d-ngv2 template field recommended for unsigned fs-verity digests */ + if (!result && entry->action == MEASURE && + entry->flags & IMA_VERITY_REQUIRED) { + template_desc = entry->template ? entry->template : + ima_template_desc_current(); + check_template_field(template_desc, "d-ngv2", + "verity rules should include d-ngv2"); + } + audit_log_format(ab, "res=%d", !result); audit_log_end(ab); return result; @@ -2149,11 +2211,15 @@ int ima_policy_show(struct seq_file *m, void *v) if (entry->template) seq_printf(m, "template=%s ", entry->template->name); if (entry->flags & IMA_DIGSIG_REQUIRED) { - if (entry->flags & IMA_MODSIG_ALLOWED) + if (entry->flags & IMA_VERITY_REQUIRED) + seq_puts(m, "appraise_type=sigv3 "); + else if (entry->flags & IMA_MODSIG_ALLOWED) seq_puts(m, "appraise_type=imasig|modsig "); else seq_puts(m, "appraise_type=imasig "); } + if (entry->flags & IMA_VERITY_REQUIRED) + seq_puts(m, "digest_type=verity "); if (entry->flags & IMA_CHECK_BLACKLIST) seq_puts(m, "appraise_flag=check_blacklist "); if (entry->flags & IMA_PERMIT_DIRECTIO) diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index db1ad6d7a57f..c25079faa208 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -20,6 +20,8 @@ static struct ima_template_desc builtin_templates[] = { {.name = IMA_TEMPLATE_IMA_NAME, .fmt = IMA_TEMPLATE_IMA_FMT}, {.name = "ima-ng", .fmt = "d-ng|n-ng"}, {.name = "ima-sig", .fmt = "d-ng|n-ng|sig"}, + {.name = "ima-ngv2", .fmt = "d-ngv2|n-ng"}, + {.name = "ima-sigv2", .fmt = "d-ngv2|n-ng|sig"}, {.name = "ima-buf", .fmt = "d-ng|n-ng|buf"}, {.name = "ima-modsig", .fmt = "d-ng|n-ng|sig|d-modsig|modsig"}, {.name = "evm-sig", @@ -38,6 +40,8 @@ static const struct ima_template_field supported_fields[] = { .field_show = ima_show_template_string}, {.field_id = "d-ng", .field_init = ima_eventdigest_ng_init, .field_show = ima_show_template_digest_ng}, + {.field_id = "d-ngv2", .field_init = ima_eventdigest_ngv2_init, + .field_show = ima_show_template_digest_ngv2}, {.field_id = "n-ng", .field_init = ima_eventname_ng_init, .field_show = ima_show_template_string}, {.field_id = "sig", .field_init = ima_eventsig_init, diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c index 7155d17a3b75..c877f01a5471 100644 --- a/security/integrity/ima/ima_template_lib.c +++ b/security/integrity/ima/ima_template_lib.c @@ -24,11 +24,24 @@ static bool ima_template_hash_algo_allowed(u8 algo) enum data_formats { DATA_FMT_DIGEST = 0, DATA_FMT_DIGEST_WITH_ALGO, + DATA_FMT_DIGEST_WITH_TYPE_AND_ALGO, DATA_FMT_STRING, DATA_FMT_HEX, DATA_FMT_UINT }; +enum digest_type { + DIGEST_TYPE_IMA, + DIGEST_TYPE_VERITY, + DIGEST_TYPE__LAST +}; + +#define DIGEST_TYPE_NAME_LEN_MAX 7 /* including NUL */ +static const char * const digest_type_name[DIGEST_TYPE__LAST] = { + [DIGEST_TYPE_IMA] = "ima", + [DIGEST_TYPE_VERITY] = "verity" +}; + static int ima_write_template_field_data(const void *data, const u32 datalen, enum data_formats datafmt, struct ima_field_data *field_data) @@ -72,8 +85,9 @@ static void ima_show_template_data_ascii(struct seq_file *m, u32 buflen = field_data->len; switch (datafmt) { + case DATA_FMT_DIGEST_WITH_TYPE_AND_ALGO: case DATA_FMT_DIGEST_WITH_ALGO: - buf_ptr = strnchr(field_data->data, buflen, ':'); + buf_ptr = strrchr(field_data->data, ':'); if (buf_ptr != field_data->data) seq_printf(m, "%s", field_data->data); @@ -178,6 +192,14 @@ void ima_show_template_digest_ng(struct seq_file *m, enum ima_show_type show, field_data); } +void ima_show_template_digest_ngv2(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data) +{ + ima_show_template_field_data(m, show, + DATA_FMT_DIGEST_WITH_TYPE_AND_ALGO, + field_data); +} + void ima_show_template_string(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data) { @@ -265,26 +287,35 @@ int ima_parse_buf(void *bufstartp, void *bufendp, void **bufcurp, } static int ima_eventdigest_init_common(const u8 *digest, u32 digestsize, - u8 hash_algo, + u8 digest_type, u8 hash_algo, struct ima_field_data *field_data) { /* * digest formats: * - DATA_FMT_DIGEST: digest - * - DATA_FMT_DIGEST_WITH_ALGO: [] + ':' + '\0' + digest, - * where is provided if the hash algorithm is not - * SHA1 or MD5 + * - DATA_FMT_DIGEST_WITH_ALGO: + ':' + '\0' + digest, + * - DATA_FMT_DIGEST_WITH_TYPE_AND_ALGO: + * + ':' + + ':' + '\0' + digest, + * + * where 'DATA_FMT_DIGEST' is the original digest format ('d') + * with a hash size limitation of 20 bytes, + * where is either "ima" or "verity", + * where is the hash_algo_name[] string. */ - u8 buffer[CRYPTO_MAX_ALG_NAME + 2 + IMA_MAX_DIGEST_SIZE] = { 0 }; + u8 buffer[DIGEST_TYPE_NAME_LEN_MAX + CRYPTO_MAX_ALG_NAME + 2 + + IMA_MAX_DIGEST_SIZE] = { 0 }; enum data_formats fmt = DATA_FMT_DIGEST; u32 offset = 0; - if (hash_algo < HASH_ALGO__LAST) { + if (digest_type < DIGEST_TYPE__LAST && hash_algo < HASH_ALGO__LAST) { + fmt = DATA_FMT_DIGEST_WITH_TYPE_AND_ALGO; + offset += 1 + sprintf(buffer, "%s:%s:", + digest_type_name[digest_type], + hash_algo_name[hash_algo]); + } else if (hash_algo < HASH_ALGO__LAST) { fmt = DATA_FMT_DIGEST_WITH_ALGO; - offset += snprintf(buffer, CRYPTO_MAX_ALG_NAME + 1, "%s", - hash_algo_name[hash_algo]); - buffer[offset] = ':'; - offset += 2; + offset += 1 + sprintf(buffer, "%s:", + hash_algo_name[hash_algo]); } if (digest) @@ -359,7 +390,8 @@ int ima_eventdigest_init(struct ima_event_data *event_data, cur_digestsize = hash.hdr.length; out: return ima_eventdigest_init_common(cur_digest, cur_digestsize, - HASH_ALGO__LAST, field_data); + DIGEST_TYPE__LAST, HASH_ALGO__LAST, + field_data); } /* @@ -368,7 +400,7 @@ out: int ima_eventdigest_ng_init(struct ima_event_data *event_data, struct ima_field_data *field_data) { - u8 *cur_digest = NULL, hash_algo = HASH_ALGO_SHA1; + u8 *cur_digest = NULL, hash_algo = ima_hash_algo; u32 cur_digestsize = 0; if (event_data->violation) /* recording a violation. */ @@ -380,7 +412,34 @@ int ima_eventdigest_ng_init(struct ima_event_data *event_data, hash_algo = event_data->iint->ima_hash->algo; out: return ima_eventdigest_init_common(cur_digest, cur_digestsize, - hash_algo, field_data); + DIGEST_TYPE__LAST, hash_algo, + field_data); +} + +/* + * This function writes the digest of an event (without size limit), + * prefixed with both the digest type and hash algorithm. + */ +int ima_eventdigest_ngv2_init(struct ima_event_data *event_data, + struct ima_field_data *field_data) +{ + u8 *cur_digest = NULL, hash_algo = ima_hash_algo; + u32 cur_digestsize = 0; + u8 digest_type = DIGEST_TYPE_IMA; + + if (event_data->violation) /* recording a violation. */ + goto out; + + cur_digest = event_data->iint->ima_hash->digest; + cur_digestsize = event_data->iint->ima_hash->length; + + hash_algo = event_data->iint->ima_hash->algo; + if (event_data->iint->flags & IMA_VERITY_REQUIRED) + digest_type = DIGEST_TYPE_VERITY; +out: + return ima_eventdigest_init_common(cur_digest, cur_digestsize, + digest_type, hash_algo, + field_data); } /* @@ -415,7 +474,8 @@ int ima_eventdigest_modsig_init(struct ima_event_data *event_data, } return ima_eventdigest_init_common(cur_digest, cur_digestsize, - hash_algo, field_data); + DIGEST_TYPE__LAST, hash_algo, + field_data); } static int ima_eventname_init_common(struct ima_event_data *event_data, @@ -475,7 +535,9 @@ int ima_eventsig_init(struct ima_event_data *event_data, { struct evm_ima_xattr_data *xattr_value = event_data->xattr_value; - if ((!xattr_value) || (xattr_value->type != EVM_IMA_XATTR_DIGSIG)) + if (!xattr_value || + (xattr_value->type != EVM_IMA_XATTR_DIGSIG && + xattr_value->type != IMA_VERITY_DIGSIG)) return ima_eventevmsig_init(event_data, field_data); return ima_write_template_field_data(xattr_value, event_data->xattr_len, diff --git a/security/integrity/ima/ima_template_lib.h b/security/integrity/ima/ima_template_lib.h index c71f1de95753..9f7c335f304f 100644 --- a/security/integrity/ima/ima_template_lib.h +++ b/security/integrity/ima/ima_template_lib.h @@ -21,6 +21,8 @@ void ima_show_template_digest(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data); void ima_show_template_digest_ng(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data); +void ima_show_template_digest_ngv2(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data); void ima_show_template_string(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data); void ima_show_template_sig(struct seq_file *m, enum ima_show_type show, @@ -38,6 +40,8 @@ int ima_eventname_init(struct ima_event_data *event_data, struct ima_field_data *field_data); int ima_eventdigest_ng_init(struct ima_event_data *event_data, struct ima_field_data *field_data); +int ima_eventdigest_ngv2_init(struct ima_event_data *event_data, + struct ima_field_data *field_data); int ima_eventdigest_modsig_init(struct ima_event_data *event_data, struct ima_field_data *field_data); int ima_eventname_ng_init(struct ima_event_data *event_data, diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 3510e413ea17..7167a6e99bdc 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -40,6 +40,7 @@ #define IMA_FAIL_UNVERIFIABLE_SIGS 0x10000000 #define IMA_MODSIG_ALLOWED 0x20000000 #define IMA_CHECK_BLACKLIST 0x40000000 +#define IMA_VERITY_REQUIRED 0x80000000 #define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT | \ IMA_HASH | IMA_APPRAISE_SUBMASK) @@ -78,6 +79,7 @@ enum evm_ima_xattr_type { EVM_IMA_XATTR_DIGSIG, IMA_XATTR_DIGEST_NG, EVM_XATTR_PORTABLE_DIGSIG, + IMA_VERITY_DIGSIG, IMA_XATTR_LAST }; @@ -92,7 +94,7 @@ struct evm_xattr { u8 digest[SHA1_DIGEST_SIZE]; } __packed; -#define IMA_MAX_DIGEST_SIZE 64 +#define IMA_MAX_DIGEST_SIZE HASH_MAX_DIGESTSIZE struct ima_digest_data { u8 algo; @@ -121,7 +123,14 @@ struct ima_max_digest_data { } __packed; /* - * signature format v2 - for using with asymmetric keys + * signature header format v2 - for using with asymmetric keys + * + * The signature_v2_hdr struct includes a signature format version + * to simplify defining new signature formats. + * + * signature format: + * version 2: regular file data hash based signature + * version 3: struct ima_file_id data based signature */ struct signature_v2_hdr { uint8_t type; /* xattr type */ @@ -132,6 +141,20 @@ struct signature_v2_hdr { uint8_t sig[]; /* signature payload */ } __packed; +/* + * IMA signature version 3 disambiguates the data that is signed, by + * indirectly signing the hash of the ima_file_id structure data, + * containing either the fsverity_descriptor struct digest or, in the + * future, the regular IMA file hash. + * + * (The hash of the ima_file_id structure is only of the portion used.) + */ +struct ima_file_id { + __u8 hash_type; /* xattr type [enum evm_ima_xattr_type] */ + __u8 hash_algorithm; /* Digest algorithm [enum hash_algo] */ + __u8 hash[HASH_MAX_DIGESTSIZE]; +} __packed; + /* integrity data associated with an inode */ struct integrity_iint_cache { struct rb_node rb_node; /* rooted in integrity_iint_tree */ diff --git a/security/integrity/platform_certs/keyring_handler.c b/security/integrity/platform_certs/keyring_handler.c index 1db4d3b4356d..8a1124e4d769 100644 --- a/security/integrity/platform_certs/keyring_handler.c +++ b/security/integrity/platform_certs/keyring_handler.c @@ -16,35 +16,13 @@ static efi_guid_t efi_cert_x509_sha256_guid __initdata = EFI_CERT_X509_SHA256_GUID; static efi_guid_t efi_cert_sha256_guid __initdata = EFI_CERT_SHA256_GUID; -/* - * Blacklist a hash. - */ -static __init void uefi_blacklist_hash(const char *source, const void *data, - size_t len, const char *type, - size_t type_len) -{ - char *hash, *p; - - hash = kmalloc(type_len + len * 2 + 1, GFP_KERNEL); - if (!hash) - return; - p = memcpy(hash, type, type_len); - p += type_len; - bin2hex(p, data, len); - p += len * 2; - *p = 0; - - mark_hash_blacklisted(hash); - kfree(hash); -} - /* * Blacklist an X509 TBS hash. */ static __init void uefi_blacklist_x509_tbs(const char *source, const void *data, size_t len) { - uefi_blacklist_hash(source, data, len, "tbs:", 4); + mark_hash_blacklisted(data, len, BLACKLIST_HASH_X509_TBS); } /* @@ -53,7 +31,7 @@ static __init void uefi_blacklist_x509_tbs(const char *source, static __init void uefi_blacklist_binary(const char *source, const void *data, size_t len) { - uefi_blacklist_hash(source, data, len, "bin:", 4); + mark_hash_blacklisted(data, len, BLACKLIST_HASH_BINARY); } /* @@ -73,7 +51,7 @@ __init efi_element_handler_t get_handler_for_db(const efi_guid_t *sig_type) { if (efi_guidcmp(*sig_type, efi_cert_x509_guid) == 0) return add_to_platform_keyring; - return 0; + return NULL; } /* @@ -88,7 +66,7 @@ __init efi_element_handler_t get_handler_for_mok(const efi_guid_t *sig_type) else return add_to_platform_keyring; } - return 0; + return NULL; } /* @@ -103,5 +81,5 @@ __init efi_element_handler_t get_handler_for_dbx(const efi_guid_t *sig_type) return uefi_blacklist_binary; if (efi_guidcmp(*sig_type, efi_cert_x509_guid) == 0) return uefi_revocation_list_x509; - return 0; + return NULL; } diff --git a/security/integrity/platform_certs/keyring_handler.h b/security/integrity/platform_certs/keyring_handler.h index 284558f30411..212d894a8c0c 100644 --- a/security/integrity/platform_certs/keyring_handler.h +++ b/security/integrity/platform_certs/keyring_handler.h @@ -35,3 +35,11 @@ efi_element_handler_t get_handler_for_mok(const efi_guid_t *sig_type); efi_element_handler_t get_handler_for_dbx(const efi_guid_t *sig_type); #endif + +#ifndef UEFI_QUIRK_SKIP_CERT +#define UEFI_QUIRK_SKIP_CERT(vendor, product) \ + .matches = { \ + DMI_MATCH(DMI_BOARD_VENDOR, vendor), \ + DMI_MATCH(DMI_PRODUCT_NAME, product), \ + }, +#endif diff --git a/security/integrity/platform_certs/load_uefi.c b/security/integrity/platform_certs/load_uefi.c index 5f45c3c07dbd..093894a640dc 100644 --- a/security/integrity/platform_certs/load_uefi.c +++ b/security/integrity/platform_certs/load_uefi.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -12,6 +13,31 @@ #include "../integrity.h" #include "keyring_handler.h" +/* + * On T2 Macs reading the db and dbx efi variables to load UEFI Secure Boot + * certificates causes occurrence of a page fault in Apple's firmware and + * a crash disabling EFI runtime services. The following quirk skips reading + * these variables. + */ +static const struct dmi_system_id uefi_skip_cert[] = { + { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookPro15,1") }, + { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookPro15,2") }, + { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookPro15,3") }, + { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookPro15,4") }, + { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookPro16,1") }, + { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookPro16,2") }, + { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookPro16,3") }, + { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookPro16,4") }, + { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookAir8,1") }, + { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookAir8,2") }, + { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookAir9,1") }, + { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacMini8,1") }, + { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacPro7,1") }, + { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "iMac20,1") }, + { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "iMac20,2") }, + { } +}; + /* * Look to see if a UEFI variable called MokIgnoreDB exists and return true if * it does. @@ -138,6 +164,13 @@ static int __init load_uefi_certs(void) unsigned long dbsize = 0, dbxsize = 0, mokxsize = 0; efi_status_t status; int rc = 0; + const struct dmi_system_id *dmi_id; + + dmi_id = dmi_first_match(uefi_skip_cert); + if (dmi_id) { + pr_err("Reading UEFI Secure Boot Certs is not supported on T2 Macs.\n"); + return false; + } if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) return false; diff --git a/security/keys/Kconfig b/security/keys/Kconfig index 0e30b361e1c1..abb03a1b2a5c 100644 --- a/security/keys/Kconfig +++ b/security/keys/Kconfig @@ -70,23 +70,19 @@ config BIG_KEYS config TRUSTED_KEYS tristate "TRUSTED KEYS" - depends on KEYS && TCG_TPM - select CRYPTO - select CRYPTO_HMAC - select CRYPTO_SHA1 - select CRYPTO_HASH_INFO - select ASN1_ENCODER - select OID_REGISTRY - select ASN1 + depends on KEYS help This option provides support for creating, sealing, and unsealing keys in the kernel. Trusted keys are random number symmetric keys, - generated and RSA-sealed by the TPM. The TPM only unseals the keys, - if the boot PCRs and other criteria match. Userspace will only ever - see encrypted blobs. + generated and sealed by a trust source selected at kernel boot-time. + Userspace will only ever see encrypted blobs. If you are unsure as to whether this is required, answer N. +if TRUSTED_KEYS +source "security/keys/trusted-keys/Kconfig" +endif + config ENCRYPTED_KEYS tristate "ENCRYPTED KEYS" depends on KEYS diff --git a/security/keys/trusted-keys/Kconfig b/security/keys/trusted-keys/Kconfig new file mode 100644 index 000000000000..dbfdd8536468 --- /dev/null +++ b/security/keys/trusted-keys/Kconfig @@ -0,0 +1,38 @@ +config TRUSTED_KEYS_TPM + bool "TPM-based trusted keys" + depends on TCG_TPM >= TRUSTED_KEYS + default y + select CRYPTO + select CRYPTO_HMAC + select CRYPTO_SHA1 + select CRYPTO_HASH_INFO + select ASN1_ENCODER + select OID_REGISTRY + select ASN1 + help + Enable use of the Trusted Platform Module (TPM) as trusted key + backend. Trusted keys are random number symmetric keys, + which will be generated and RSA-sealed by the TPM. + The TPM only unseals the keys, if the boot PCRs and other + criteria match. + +config TRUSTED_KEYS_TEE + bool "TEE-based trusted keys" + depends on TEE >= TRUSTED_KEYS + default y + help + Enable use of the Trusted Execution Environment (TEE) as trusted + key backend. + +config TRUSTED_KEYS_CAAM + bool "CAAM-based trusted keys" + depends on CRYPTO_DEV_FSL_CAAM_JR >= TRUSTED_KEYS + select CRYPTO_DEV_FSL_CAAM_BLOB_GEN + default y + help + Enable use of NXP's Cryptographic Accelerator and Assurance Module + (CAAM) as trusted key backend. + +if !TRUSTED_KEYS_TPM && !TRUSTED_KEYS_TEE && !TRUSTED_KEYS_CAAM +comment "No trust source selected!" +endif diff --git a/security/keys/trusted-keys/Makefile b/security/keys/trusted-keys/Makefile index feb8b6c3cc79..735aa0bc08ef 100644 --- a/security/keys/trusted-keys/Makefile +++ b/security/keys/trusted-keys/Makefile @@ -5,10 +5,12 @@ obj-$(CONFIG_TRUSTED_KEYS) += trusted.o trusted-y += trusted_core.o -trusted-y += trusted_tpm1.o +trusted-$(CONFIG_TRUSTED_KEYS_TPM) += trusted_tpm1.o $(obj)/trusted_tpm2.o: $(obj)/tpm2key.asn1.h -trusted-y += trusted_tpm2.o -trusted-y += tpm2key.asn1.o +trusted-$(CONFIG_TRUSTED_KEYS_TPM) += trusted_tpm2.o +trusted-$(CONFIG_TRUSTED_KEYS_TPM) += tpm2key.asn1.o -trusted-$(CONFIG_TEE) += trusted_tee.o +trusted-$(CONFIG_TRUSTED_KEYS_TEE) += trusted_tee.o + +trusted-$(CONFIG_TRUSTED_KEYS_CAAM) += trusted_caam.o diff --git a/security/keys/trusted-keys/trusted_caam.c b/security/keys/trusted-keys/trusted_caam.c new file mode 100644 index 000000000000..e3415c520c0a --- /dev/null +++ b/security/keys/trusted-keys/trusted_caam.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021 Pengutronix, Ahmad Fatoum + */ + +#include +#include +#include +#include +#include + +static struct caam_blob_priv *blobifier; + +#define KEYMOD "SECURE_KEY" + +static_assert(MAX_KEY_SIZE + CAAM_BLOB_OVERHEAD <= CAAM_BLOB_MAX_LEN); +static_assert(MAX_BLOB_SIZE <= CAAM_BLOB_MAX_LEN); + +static int trusted_caam_seal(struct trusted_key_payload *p, char *datablob) +{ + int ret; + struct caam_blob_info info = { + .input = p->key, .input_len = p->key_len, + .output = p->blob, .output_len = MAX_BLOB_SIZE, + .key_mod = KEYMOD, .key_mod_len = sizeof(KEYMOD) - 1, + }; + + ret = caam_encap_blob(blobifier, &info); + if (ret) + return ret; + + p->blob_len = info.output_len; + return 0; +} + +static int trusted_caam_unseal(struct trusted_key_payload *p, char *datablob) +{ + int ret; + struct caam_blob_info info = { + .input = p->blob, .input_len = p->blob_len, + .output = p->key, .output_len = MAX_KEY_SIZE, + .key_mod = KEYMOD, .key_mod_len = sizeof(KEYMOD) - 1, + }; + + ret = caam_decap_blob(blobifier, &info); + if (ret) + return ret; + + p->key_len = info.output_len; + return 0; +} + +static int trusted_caam_init(void) +{ + int ret; + + blobifier = caam_blob_gen_init(); + if (IS_ERR(blobifier)) + return PTR_ERR(blobifier); + + ret = register_key_type(&key_type_trusted); + if (ret) + caam_blob_gen_exit(blobifier); + + return ret; +} + +static void trusted_caam_exit(void) +{ + unregister_key_type(&key_type_trusted); + caam_blob_gen_exit(blobifier); +} + +struct trusted_key_ops trusted_key_caam_ops = { + .migratable = 0, /* non-migratable */ + .init = trusted_caam_init, + .seal = trusted_caam_seal, + .unseal = trusted_caam_unseal, + .exit = trusted_caam_exit, +}; diff --git a/security/keys/trusted-keys/trusted_core.c b/security/keys/trusted-keys/trusted_core.c index 9b9d3ef79cbe..c6fc50d67214 100644 --- a/security/keys/trusted-keys/trusted_core.c +++ b/security/keys/trusted-keys/trusted_core.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -16,23 +17,31 @@ #include #include #include +#include #include #include #include #include #include +static char *trusted_rng = "default"; +module_param_named(rng, trusted_rng, charp, 0); +MODULE_PARM_DESC(rng, "Select trusted key RNG"); + static char *trusted_key_source; module_param_named(source, trusted_key_source, charp, 0); -MODULE_PARM_DESC(source, "Select trusted keys source (tpm or tee)"); +MODULE_PARM_DESC(source, "Select trusted keys source (tpm, tee or caam)"); static const struct trusted_key_source trusted_key_sources[] = { -#if IS_REACHABLE(CONFIG_TCG_TPM) +#if defined(CONFIG_TRUSTED_KEYS_TPM) { "tpm", &trusted_key_tpm_ops }, #endif -#if IS_REACHABLE(CONFIG_TEE) +#if defined(CONFIG_TRUSTED_KEYS_TEE) { "tee", &trusted_key_tee_ops }, #endif +#if defined(CONFIG_TRUSTED_KEYS_CAAM) + { "caam", &trusted_key_caam_ops }, +#endif }; DEFINE_STATIC_CALL_NULL(trusted_key_init, *trusted_key_sources[0].ops->init); @@ -312,8 +321,14 @@ struct key_type key_type_trusted = { }; EXPORT_SYMBOL_GPL(key_type_trusted); +static int kernel_get_random(unsigned char *key, size_t key_len) +{ + return get_random_bytes_wait(key, key_len) ?: key_len; +} + static int __init init_trusted(void) { + int (*get_random)(unsigned char *key, size_t key_len); int i, ret = 0; for (i = 0; i < ARRAY_SIZE(trusted_key_sources); i++) { @@ -322,6 +337,28 @@ static int __init init_trusted(void) strlen(trusted_key_sources[i].name))) continue; + /* + * We always support trusted.rng="kernel" and "default" as + * well as trusted.rng=$trusted.source if the trust source + * defines its own get_random callback. + */ + get_random = trusted_key_sources[i].ops->get_random; + if (trusted_rng && strcmp(trusted_rng, "default")) { + if (!strcmp(trusted_rng, "kernel")) { + get_random = kernel_get_random; + } else if (strcmp(trusted_rng, trusted_key_sources[i].name) || + !get_random) { + pr_warn("Unsupported RNG. Supported: kernel"); + if (get_random) + pr_cont(", %s", trusted_key_sources[i].name); + pr_cont(", default\n"); + return -EINVAL; + } + } + + if (!get_random) + get_random = kernel_get_random; + static_call_update(trusted_key_init, trusted_key_sources[i].ops->init); static_call_update(trusted_key_seal, @@ -329,7 +366,7 @@ static int __init init_trusted(void) static_call_update(trusted_key_unseal, trusted_key_sources[i].ops->unseal); static_call_update(trusted_key_get_random, - trusted_key_sources[i].ops->get_random); + get_random); static_call_update(trusted_key_exit, trusted_key_sources[i].ops->exit); migratable = trusted_key_sources[i].ops->migratable; diff --git a/security/landlock/cred.c b/security/landlock/cred.c index 6725af24c684..ec6c37f04a19 100644 --- a/security/landlock/cred.c +++ b/security/landlock/cred.c @@ -15,7 +15,7 @@ #include "setup.h" static int hook_cred_prepare(struct cred *const new, - const struct cred *const old, const gfp_t gfp) + const struct cred *const old, const gfp_t gfp) { struct landlock_ruleset *const old_dom = landlock_cred(old)->domain; @@ -42,5 +42,5 @@ static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = { __init void landlock_add_cred_hooks(void) { security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), - LANDLOCK_NAME); + LANDLOCK_NAME); } diff --git a/security/landlock/cred.h b/security/landlock/cred.h index 5f99d3decade..af89ab00e6d1 100644 --- a/security/landlock/cred.h +++ b/security/landlock/cred.h @@ -20,8 +20,8 @@ struct landlock_cred_security { struct landlock_ruleset *domain; }; -static inline struct landlock_cred_security *landlock_cred( - const struct cred *cred) +static inline struct landlock_cred_security * +landlock_cred(const struct cred *cred) { return cred->security + landlock_blob_sizes.lbs_cred; } @@ -34,8 +34,8 @@ static inline const struct landlock_ruleset *landlock_get_current_domain(void) /* * The call needs to come from an RCU read-side critical section. */ -static inline const struct landlock_ruleset *landlock_get_task_domain( - const struct task_struct *const task) +static inline const struct landlock_ruleset * +landlock_get_task_domain(const struct task_struct *const task) { return landlock_cred(__task_cred(task))->domain; } diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 97b8e421f617..ec5a6247cd3e 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -4,6 +4,7 @@ * * Copyright © 2016-2020 Mickaël Salaün * Copyright © 2018-2020 ANSSI + * Copyright © 2021-2022 Microsoft Corporation */ #include @@ -141,23 +142,26 @@ retry: } /* All access rights that can be tied to files. */ +/* clang-format off */ #define ACCESS_FILE ( \ LANDLOCK_ACCESS_FS_EXECUTE | \ LANDLOCK_ACCESS_FS_WRITE_FILE | \ LANDLOCK_ACCESS_FS_READ_FILE) +/* clang-format on */ /* * @path: Should have been checked by get_path_from_fd(). */ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, - const struct path *const path, u32 access_rights) + const struct path *const path, + access_mask_t access_rights) { int err; struct landlock_object *object; /* Files only get access rights that make sense. */ - if (!d_is_dir(path->dentry) && (access_rights | ACCESS_FILE) != - ACCESS_FILE) + if (!d_is_dir(path->dentry) && + (access_rights | ACCESS_FILE) != ACCESS_FILE) return -EINVAL; if (WARN_ON_ONCE(ruleset->num_layers != 1)) return -EINVAL; @@ -180,84 +184,352 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, /* Access-control management */ -static inline u64 unmask_layers( - const struct landlock_ruleset *const domain, - const struct path *const path, const u32 access_request, - u64 layer_mask) +/* + * The lifetime of the returned rule is tied to @domain. + * + * Returns NULL if no rule is found or if @dentry is negative. + */ +static inline const struct landlock_rule * +find_rule(const struct landlock_ruleset *const domain, + const struct dentry *const dentry) { const struct landlock_rule *rule; const struct inode *inode; - size_t i; - if (d_is_negative(path->dentry)) - /* Ignore nonexistent leafs. */ - return layer_mask; - inode = d_backing_inode(path->dentry); + /* Ignores nonexistent leafs. */ + if (d_is_negative(dentry)) + return NULL; + + inode = d_backing_inode(dentry); rcu_read_lock(); - rule = landlock_find_rule(domain, - rcu_dereference(landlock_inode(inode)->object)); + rule = landlock_find_rule( + domain, rcu_dereference(landlock_inode(inode)->object)); rcu_read_unlock(); + return rule; +} + +/* + * @layer_masks is read and may be updated according to the access request and + * the matching rule. + * + * Returns true if the request is allowed (i.e. relevant layer masks for the + * request are empty). + */ +static inline bool +unmask_layers(const struct landlock_rule *const rule, + const access_mask_t access_request, + layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) +{ + size_t layer_level; + + if (!access_request || !layer_masks) + return true; if (!rule) - return layer_mask; + return false; /* * An access is granted if, for each policy layer, at least one rule - * encountered on the pathwalk grants the requested accesses, - * regardless of their position in the layer stack. We must then check + * encountered on the pathwalk grants the requested access, + * regardless of its position in the layer stack. We must then check * the remaining layers for each inode, from the first added layer to - * the last one. + * the last one. When there is multiple requested accesses, for each + * policy layer, the full set of requested accesses may not be granted + * by only one rule, but by the union (binary OR) of multiple rules. + * E.g. /a/b + /a => /a/b */ - for (i = 0; i < rule->num_layers; i++) { - const struct landlock_layer *const layer = &rule->layers[i]; - const u64 layer_level = BIT_ULL(layer->level - 1); + for (layer_level = 0; layer_level < rule->num_layers; layer_level++) { + const struct landlock_layer *const layer = + &rule->layers[layer_level]; + const layer_mask_t layer_bit = BIT_ULL(layer->level - 1); + const unsigned long access_req = access_request; + unsigned long access_bit; + bool is_empty; - /* Checks that the layer grants access to the full request. */ - if ((layer->access & access_request) == access_request) { - layer_mask &= ~layer_level; - - if (layer_mask == 0) - return layer_mask; + /* + * Records in @layer_masks which layer grants access to each + * requested access. + */ + is_empty = true; + for_each_set_bit(access_bit, &access_req, + ARRAY_SIZE(*layer_masks)) { + if (layer->access & BIT_ULL(access_bit)) + (*layer_masks)[access_bit] &= ~layer_bit; + is_empty = is_empty && !(*layer_masks)[access_bit]; } + if (is_empty) + return true; } - return layer_mask; + return false; } -static int check_access_path(const struct landlock_ruleset *const domain, - const struct path *const path, u32 access_request) +/* + * Allows access to pseudo filesystems that will never be mountable (e.g. + * sockfs, pipefs), but can still be reachable through + * /proc//fd/ + */ +static inline bool is_nouser_or_private(const struct dentry *dentry) { - bool allowed = false; - struct path walker_path; - u64 layer_mask; - size_t i; + return (dentry->d_sb->s_flags & SB_NOUSER) || + (d_is_positive(dentry) && + unlikely(IS_PRIVATE(d_backing_inode(dentry)))); +} - /* Make sure all layers can be checked. */ - BUILD_BUG_ON(BITS_PER_TYPE(layer_mask) < LANDLOCK_MAX_NUM_LAYERS); +static inline access_mask_t +get_handled_accesses(const struct landlock_ruleset *const domain) +{ + access_mask_t access_dom = 0; + unsigned long access_bit; + for (access_bit = 0; access_bit < LANDLOCK_NUM_ACCESS_FS; + access_bit++) { + size_t layer_level; + + for (layer_level = 0; layer_level < domain->num_layers; + layer_level++) { + if (domain->fs_access_masks[layer_level] & + BIT_ULL(access_bit)) { + access_dom |= BIT_ULL(access_bit); + break; + } + } + } + return access_dom; +} + +static inline access_mask_t +init_layer_masks(const struct landlock_ruleset *const domain, + const access_mask_t access_request, + layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) +{ + access_mask_t handled_accesses = 0; + size_t layer_level; + + memset(layer_masks, 0, sizeof(*layer_masks)); + /* An empty access request can happen because of O_WRONLY | O_RDWR. */ if (!access_request) return 0; + + /* Saves all handled accesses per layer. */ + for (layer_level = 0; layer_level < domain->num_layers; layer_level++) { + const unsigned long access_req = access_request; + unsigned long access_bit; + + for_each_set_bit(access_bit, &access_req, + ARRAY_SIZE(*layer_masks)) { + if (domain->fs_access_masks[layer_level] & + BIT_ULL(access_bit)) { + (*layer_masks)[access_bit] |= + BIT_ULL(layer_level); + handled_accesses |= BIT_ULL(access_bit); + } + } + } + return handled_accesses; +} + +/* + * Check that a destination file hierarchy has more restrictions than a source + * file hierarchy. This is only used for link and rename actions. + * + * @layer_masks_child2: Optional child masks. + */ +static inline bool no_more_access( + const layer_mask_t (*const layer_masks_parent1)[LANDLOCK_NUM_ACCESS_FS], + const layer_mask_t (*const layer_masks_child1)[LANDLOCK_NUM_ACCESS_FS], + const bool child1_is_directory, + const layer_mask_t (*const layer_masks_parent2)[LANDLOCK_NUM_ACCESS_FS], + const layer_mask_t (*const layer_masks_child2)[LANDLOCK_NUM_ACCESS_FS], + const bool child2_is_directory) +{ + unsigned long access_bit; + + for (access_bit = 0; access_bit < ARRAY_SIZE(*layer_masks_parent2); + access_bit++) { + /* Ignores accesses that only make sense for directories. */ + const bool is_file_access = + !!(BIT_ULL(access_bit) & ACCESS_FILE); + + if (child1_is_directory || is_file_access) { + /* + * Checks if the destination restrictions are a + * superset of the source ones (i.e. inherited access + * rights without child exceptions): + * restrictions(parent2) >= restrictions(child1) + */ + if ((((*layer_masks_parent1)[access_bit] & + (*layer_masks_child1)[access_bit]) | + (*layer_masks_parent2)[access_bit]) != + (*layer_masks_parent2)[access_bit]) + return false; + } + + if (!layer_masks_child2) + continue; + if (child2_is_directory || is_file_access) { + /* + * Checks inverted restrictions for RENAME_EXCHANGE: + * restrictions(parent1) >= restrictions(child2) + */ + if ((((*layer_masks_parent2)[access_bit] & + (*layer_masks_child2)[access_bit]) | + (*layer_masks_parent1)[access_bit]) != + (*layer_masks_parent1)[access_bit]) + return false; + } + } + return true; +} + +/* + * Removes @layer_masks accesses that are not requested. + * + * Returns true if the request is allowed, false otherwise. + */ +static inline bool +scope_to_request(const access_mask_t access_request, + layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) +{ + const unsigned long access_req = access_request; + unsigned long access_bit; + + if (WARN_ON_ONCE(!layer_masks)) + return true; + + for_each_clear_bit(access_bit, &access_req, ARRAY_SIZE(*layer_masks)) + (*layer_masks)[access_bit] = 0; + return !memchr_inv(layer_masks, 0, sizeof(*layer_masks)); +} + +/* + * Returns true if there is at least one access right different than + * LANDLOCK_ACCESS_FS_REFER. + */ +static inline bool +is_eacces(const layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS], + const access_mask_t access_request) +{ + unsigned long access_bit; + /* LANDLOCK_ACCESS_FS_REFER alone must return -EXDEV. */ + const unsigned long access_check = access_request & + ~LANDLOCK_ACCESS_FS_REFER; + + if (!layer_masks) + return false; + + for_each_set_bit(access_bit, &access_check, ARRAY_SIZE(*layer_masks)) { + if ((*layer_masks)[access_bit]) + return true; + } + return false; +} + +/** + * check_access_path_dual - Check accesses for requests with a common path + * + * @domain: Domain to check against. + * @path: File hierarchy to walk through. + * @access_request_parent1: Accesses to check, once @layer_masks_parent1 is + * equal to @layer_masks_parent2 (if any). This is tied to the unique + * requested path for most actions, or the source in case of a refer action + * (i.e. rename or link), or the source and destination in case of + * RENAME_EXCHANGE. + * @layer_masks_parent1: Pointer to a matrix of layer masks per access + * masks, identifying the layers that forbid a specific access. Bits from + * this matrix can be unset according to the @path walk. An empty matrix + * means that @domain allows all possible Landlock accesses (i.e. not only + * those identified by @access_request_parent1). This matrix can + * initially refer to domain layer masks and, when the accesses for the + * destination and source are the same, to requested layer masks. + * @dentry_child1: Dentry to the initial child of the parent1 path. This + * pointer must be NULL for non-refer actions (i.e. not link nor rename). + * @access_request_parent2: Similar to @access_request_parent1 but for a + * request involving a source and a destination. This refers to the + * destination, except in case of RENAME_EXCHANGE where it also refers to + * the source. Must be set to 0 when using a simple path request. + * @layer_masks_parent2: Similar to @layer_masks_parent1 but for a refer + * action. This must be NULL otherwise. + * @dentry_child2: Dentry to the initial child of the parent2 path. This + * pointer is only set for RENAME_EXCHANGE actions and must be NULL + * otherwise. + * + * This helper first checks that the destination has a superset of restrictions + * compared to the source (if any) for a common path. Because of + * RENAME_EXCHANGE actions, source and destinations may be swapped. It then + * checks that the collected accesses and the remaining ones are enough to + * allow the request. + * + * Returns: + * - 0 if the access request is granted; + * - -EACCES if it is denied because of access right other than + * LANDLOCK_ACCESS_FS_REFER; + * - -EXDEV if the renaming or linking would be a privileged escalation + * (according to each layered policies), or if LANDLOCK_ACCESS_FS_REFER is + * not allowed by the source or the destination. + */ +static int check_access_path_dual( + const struct landlock_ruleset *const domain, + const struct path *const path, + const access_mask_t access_request_parent1, + layer_mask_t (*const layer_masks_parent1)[LANDLOCK_NUM_ACCESS_FS], + const struct dentry *const dentry_child1, + const access_mask_t access_request_parent2, + layer_mask_t (*const layer_masks_parent2)[LANDLOCK_NUM_ACCESS_FS], + const struct dentry *const dentry_child2) +{ + bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check, + child1_is_directory = true, child2_is_directory = true; + struct path walker_path; + access_mask_t access_masked_parent1, access_masked_parent2; + layer_mask_t _layer_masks_child1[LANDLOCK_NUM_ACCESS_FS], + _layer_masks_child2[LANDLOCK_NUM_ACCESS_FS]; + layer_mask_t(*layer_masks_child1)[LANDLOCK_NUM_ACCESS_FS] = NULL, + (*layer_masks_child2)[LANDLOCK_NUM_ACCESS_FS] = NULL; + + if (!access_request_parent1 && !access_request_parent2) + return 0; if (WARN_ON_ONCE(!domain || !path)) return 0; - /* - * Allows access to pseudo filesystems that will never be mountable - * (e.g. sockfs, pipefs), but can still be reachable through - * /proc//fd/ . - */ - if ((path->dentry->d_sb->s_flags & SB_NOUSER) || - (d_is_positive(path->dentry) && - unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))) + if (is_nouser_or_private(path->dentry)) return 0; - if (WARN_ON_ONCE(domain->num_layers < 1)) + if (WARN_ON_ONCE(domain->num_layers < 1 || !layer_masks_parent1)) return -EACCES; - /* Saves all layers handling a subset of requested accesses. */ - layer_mask = 0; - for (i = 0; i < domain->num_layers; i++) { - if (domain->fs_access_masks[i] & access_request) - layer_mask |= BIT_ULL(i); + if (unlikely(layer_masks_parent2)) { + if (WARN_ON_ONCE(!dentry_child1)) + return -EACCES; + /* + * For a double request, first check for potential privilege + * escalation by looking at domain handled accesses (which are + * a superset of the meaningful requested accesses). + */ + access_masked_parent1 = access_masked_parent2 = + get_handled_accesses(domain); + is_dom_check = true; + } else { + if (WARN_ON_ONCE(dentry_child1 || dentry_child2)) + return -EACCES; + /* For a simple request, only check for requested accesses. */ + access_masked_parent1 = access_request_parent1; + access_masked_parent2 = access_request_parent2; + is_dom_check = false; + } + + if (unlikely(dentry_child1)) { + unmask_layers(find_rule(domain, dentry_child1), + init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, + &_layer_masks_child1), + &_layer_masks_child1); + layer_masks_child1 = &_layer_masks_child1; + child1_is_directory = d_is_dir(dentry_child1); + } + if (unlikely(dentry_child2)) { + unmask_layers(find_rule(domain, dentry_child2), + init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, + &_layer_masks_child2), + &_layer_masks_child2); + layer_masks_child2 = &_layer_masks_child2; + child2_is_directory = d_is_dir(dentry_child2); } - /* An access request not handled by the domain is allowed. */ - if (layer_mask == 0) - return 0; walker_path = *path; path_get(&walker_path); @@ -267,15 +539,54 @@ static int check_access_path(const struct landlock_ruleset *const domain, */ while (true) { struct dentry *parent_dentry; + const struct landlock_rule *rule; - layer_mask = unmask_layers(domain, &walker_path, - access_request, layer_mask); - if (layer_mask == 0) { - /* Stops when a rule from each layer grants access. */ - allowed = true; - break; + /* + * If at least all accesses allowed on the destination are + * already allowed on the source, respectively if there is at + * least as much as restrictions on the destination than on the + * source, then we can safely refer files from the source to + * the destination without risking a privilege escalation. + * This also applies in the case of RENAME_EXCHANGE, which + * implies checks on both direction. This is crucial for + * standalone multilayered security policies. Furthermore, + * this helps avoid policy writers to shoot themselves in the + * foot. + */ + if (unlikely(is_dom_check && + no_more_access( + layer_masks_parent1, layer_masks_child1, + child1_is_directory, layer_masks_parent2, + layer_masks_child2, + child2_is_directory))) { + allowed_parent1 = scope_to_request( + access_request_parent1, layer_masks_parent1); + allowed_parent2 = scope_to_request( + access_request_parent2, layer_masks_parent2); + + /* Stops when all accesses are granted. */ + if (allowed_parent1 && allowed_parent2) + break; + + /* + * Now, downgrades the remaining checks from domain + * handled accesses to requested accesses. + */ + is_dom_check = false; + access_masked_parent1 = access_request_parent1; + access_masked_parent2 = access_request_parent2; } + rule = find_rule(domain, walker_path.dentry); + allowed_parent1 = unmask_layers(rule, access_masked_parent1, + layer_masks_parent1); + allowed_parent2 = unmask_layers(rule, access_masked_parent2, + layer_masks_parent2); + + /* Stops when a rule from each layer grants access. */ + if (allowed_parent1 && allowed_parent2) + break; + jump_up: if (walker_path.dentry == walker_path.mnt->mnt_root) { if (follow_up(&walker_path)) { @@ -286,7 +597,6 @@ jump_up: * Stops at the real root. Denies access * because not all layers have granted access. */ - allowed = false; break; } } @@ -296,7 +606,8 @@ jump_up: * access to internal filesystems (e.g. nsfs, which is * reachable through /proc//ns/). */ - allowed = !!(walker_path.mnt->mnt_flags & MNT_INTERNAL); + allowed_parent1 = allowed_parent2 = + !!(walker_path.mnt->mnt_flags & MNT_INTERNAL); break; } parent_dentry = dget_parent(walker_path.dentry); @@ -304,11 +615,40 @@ jump_up: walker_path.dentry = parent_dentry; } path_put(&walker_path); - return allowed ? 0 : -EACCES; + + if (allowed_parent1 && allowed_parent2) + return 0; + + /* + * This prioritizes EACCES over EXDEV for all actions, including + * renames with RENAME_EXCHANGE. + */ + if (likely(is_eacces(layer_masks_parent1, access_request_parent1) || + is_eacces(layer_masks_parent2, access_request_parent2))) + return -EACCES; + + /* + * Gracefully forbids reparenting if the destination directory + * hierarchy is not a superset of restrictions of the source directory + * hierarchy, or if LANDLOCK_ACCESS_FS_REFER is not allowed by the + * source or the destination. + */ + return -EXDEV; +} + +static inline int check_access_path(const struct landlock_ruleset *const domain, + const struct path *const path, + access_mask_t access_request) +{ + layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {}; + + access_request = init_layer_masks(domain, access_request, &layer_masks); + return check_access_path_dual(domain, path, access_request, + &layer_masks, NULL, 0, NULL, NULL); } static inline int current_check_access_path(const struct path *const path, - const u32 access_request) + const access_mask_t access_request) { const struct landlock_ruleset *const dom = landlock_get_current_domain(); @@ -318,6 +658,239 @@ static inline int current_check_access_path(const struct path *const path, return check_access_path(dom, path, access_request); } +static inline access_mask_t get_mode_access(const umode_t mode) +{ + switch (mode & S_IFMT) { + case S_IFLNK: + return LANDLOCK_ACCESS_FS_MAKE_SYM; + case 0: + /* A zero mode translates to S_IFREG. */ + case S_IFREG: + return LANDLOCK_ACCESS_FS_MAKE_REG; + case S_IFDIR: + return LANDLOCK_ACCESS_FS_MAKE_DIR; + case S_IFCHR: + return LANDLOCK_ACCESS_FS_MAKE_CHAR; + case S_IFBLK: + return LANDLOCK_ACCESS_FS_MAKE_BLOCK; + case S_IFIFO: + return LANDLOCK_ACCESS_FS_MAKE_FIFO; + case S_IFSOCK: + return LANDLOCK_ACCESS_FS_MAKE_SOCK; + default: + WARN_ON_ONCE(1); + return 0; + } +} + +static inline access_mask_t maybe_remove(const struct dentry *const dentry) +{ + if (d_is_negative(dentry)) + return 0; + return d_is_dir(dentry) ? LANDLOCK_ACCESS_FS_REMOVE_DIR : + LANDLOCK_ACCESS_FS_REMOVE_FILE; +} + +/** + * collect_domain_accesses - Walk through a file path and collect accesses + * + * @domain: Domain to check against. + * @mnt_root: Last directory to check. + * @dir: Directory to start the walk from. + * @layer_masks_dom: Where to store the collected accesses. + * + * This helper is useful to begin a path walk from the @dir directory to a + * @mnt_root directory used as a mount point. This mount point is the common + * ancestor between the source and the destination of a renamed and linked + * file. While walking from @dir to @mnt_root, we record all the domain's + * allowed accesses in @layer_masks_dom. + * + * This is similar to check_access_path_dual() but much simpler because it only + * handles walking on the same mount point and only check one set of accesses. + * + * Returns: + * - true if all the domain access rights are allowed for @dir; + * - false if the walk reached @mnt_root. + */ +static bool collect_domain_accesses( + const struct landlock_ruleset *const domain, + const struct dentry *const mnt_root, struct dentry *dir, + layer_mask_t (*const layer_masks_dom)[LANDLOCK_NUM_ACCESS_FS]) +{ + unsigned long access_dom; + bool ret = false; + + if (WARN_ON_ONCE(!domain || !mnt_root || !dir || !layer_masks_dom)) + return true; + if (is_nouser_or_private(dir)) + return true; + + access_dom = init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, + layer_masks_dom); + + dget(dir); + while (true) { + struct dentry *parent_dentry; + + /* Gets all layers allowing all domain accesses. */ + if (unmask_layers(find_rule(domain, dir), access_dom, + layer_masks_dom)) { + /* + * Stops when all handled accesses are allowed by at + * least one rule in each layer. + */ + ret = true; + break; + } + + /* We should not reach a root other than @mnt_root. */ + if (dir == mnt_root || WARN_ON_ONCE(IS_ROOT(dir))) + break; + + parent_dentry = dget_parent(dir); + dput(dir); + dir = parent_dentry; + } + dput(dir); + return ret; +} + +/** + * current_check_refer_path - Check if a rename or link action is allowed + * + * @old_dentry: File or directory requested to be moved or linked. + * @new_dir: Destination parent directory. + * @new_dentry: Destination file or directory. + * @removable: Sets to true if it is a rename operation. + * @exchange: Sets to true if it is a rename operation with RENAME_EXCHANGE. + * + * Because of its unprivileged constraints, Landlock relies on file hierarchies + * (and not only inodes) to tie access rights to files. Being able to link or + * rename a file hierarchy brings some challenges. Indeed, moving or linking a + * file (i.e. creating a new reference to an inode) can have an impact on the + * actions allowed for a set of files if it would change its parent directory + * (i.e. reparenting). + * + * To avoid trivial access right bypasses, Landlock first checks if the file or + * directory requested to be moved would gain new access rights inherited from + * its new hierarchy. Before returning any error, Landlock then checks that + * the parent source hierarchy and the destination hierarchy would allow the + * link or rename action. If it is not the case, an error with EACCES is + * returned to inform user space that there is no way to remove or create the + * requested source file type. If it should be allowed but the new inherited + * access rights would be greater than the source access rights, then the + * kernel returns an error with EXDEV. Prioritizing EACCES over EXDEV enables + * user space to abort the whole operation if there is no way to do it, or to + * manually copy the source to the destination if this remains allowed, e.g. + * because file creation is allowed on the destination directory but not direct + * linking. + * + * To achieve this goal, the kernel needs to compare two file hierarchies: the + * one identifying the source file or directory (including itself), and the + * destination one. This can be seen as a multilayer partial ordering problem. + * The kernel walks through these paths and collects in a matrix the access + * rights that are denied per layer. These matrices are then compared to see + * if the destination one has more (or the same) restrictions as the source + * one. If this is the case, the requested action will not return EXDEV, which + * doesn't mean the action is allowed. The parent hierarchy of the source + * (i.e. parent directory), and the destination hierarchy must also be checked + * to verify that they explicitly allow such action (i.e. referencing, + * creation and potentially removal rights). The kernel implementation is then + * required to rely on potentially four matrices of access rights: one for the + * source file or directory (i.e. the child), a potentially other one for the + * other source/destination (in case of RENAME_EXCHANGE), one for the source + * parent hierarchy and a last one for the destination hierarchy. These + * ephemeral matrices take some space on the stack, which limits the number of + * layers to a deemed reasonable number: 16. + * + * Returns: + * - 0 if access is allowed; + * - -EXDEV if @old_dentry would inherit new access rights from @new_dir; + * - -EACCES if file removal or creation is denied. + */ +static int current_check_refer_path(struct dentry *const old_dentry, + const struct path *const new_dir, + struct dentry *const new_dentry, + const bool removable, const bool exchange) +{ + const struct landlock_ruleset *const dom = + landlock_get_current_domain(); + bool allow_parent1, allow_parent2; + access_mask_t access_request_parent1, access_request_parent2; + struct path mnt_dir; + layer_mask_t layer_masks_parent1[LANDLOCK_NUM_ACCESS_FS], + layer_masks_parent2[LANDLOCK_NUM_ACCESS_FS]; + + if (!dom) + return 0; + if (WARN_ON_ONCE(dom->num_layers < 1)) + return -EACCES; + if (unlikely(d_is_negative(old_dentry))) + return -ENOENT; + if (exchange) { + if (unlikely(d_is_negative(new_dentry))) + return -ENOENT; + access_request_parent1 = + get_mode_access(d_backing_inode(new_dentry)->i_mode); + } else { + access_request_parent1 = 0; + } + access_request_parent2 = + get_mode_access(d_backing_inode(old_dentry)->i_mode); + if (removable) { + access_request_parent1 |= maybe_remove(old_dentry); + access_request_parent2 |= maybe_remove(new_dentry); + } + + /* The mount points are the same for old and new paths, cf. EXDEV. */ + if (old_dentry->d_parent == new_dir->dentry) { + /* + * The LANDLOCK_ACCESS_FS_REFER access right is not required + * for same-directory referer (i.e. no reparenting). + */ + access_request_parent1 = init_layer_masks( + dom, access_request_parent1 | access_request_parent2, + &layer_masks_parent1); + return check_access_path_dual(dom, new_dir, + access_request_parent1, + &layer_masks_parent1, NULL, 0, + NULL, NULL); + } + + /* Backward compatibility: no reparenting support. */ + if (!(get_handled_accesses(dom) & LANDLOCK_ACCESS_FS_REFER)) + return -EXDEV; + + access_request_parent1 |= LANDLOCK_ACCESS_FS_REFER; + access_request_parent2 |= LANDLOCK_ACCESS_FS_REFER; + + /* Saves the common mount point. */ + mnt_dir.mnt = new_dir->mnt; + mnt_dir.dentry = new_dir->mnt->mnt_root; + + /* new_dir->dentry is equal to new_dentry->d_parent */ + allow_parent1 = collect_domain_accesses(dom, mnt_dir.dentry, + old_dentry->d_parent, + &layer_masks_parent1); + allow_parent2 = collect_domain_accesses( + dom, mnt_dir.dentry, new_dir->dentry, &layer_masks_parent2); + + if (allow_parent1 && allow_parent2) + return 0; + + /* + * To be able to compare source and destination domain access rights, + * take into account the @old_dentry access rights aggregated with its + * parent access rights. This will be useful to compare with the + * destination parent access rights. + */ + return check_access_path_dual(dom, &mnt_dir, access_request_parent1, + &layer_masks_parent1, old_dentry, + access_request_parent2, + &layer_masks_parent2, + exchange ? new_dentry : NULL); +} + /* Inode hooks */ static void hook_inode_free_security(struct inode *const inode) @@ -436,8 +1009,8 @@ static void hook_sb_delete(struct super_block *const sb) if (prev_inode) iput(prev_inode); /* Waits for pending iput() in release_inode(). */ - wait_var_event(&landlock_superblock(sb)->inode_refs, !atomic_long_read( - &landlock_superblock(sb)->inode_refs)); + wait_var_event(&landlock_superblock(sb)->inode_refs, + !atomic_long_read(&landlock_superblock(sb)->inode_refs)); } /* @@ -459,8 +1032,8 @@ static void hook_sb_delete(struct super_block *const sb) * a dedicated user space option would be required (e.g. as a ruleset flag). */ static int hook_sb_mount(const char *const dev_name, - const struct path *const path, const char *const type, - const unsigned long flags, void *const data) + const struct path *const path, const char *const type, + const unsigned long flags, void *const data) { if (!landlock_get_current_domain()) return 0; @@ -468,7 +1041,7 @@ static int hook_sb_mount(const char *const dev_name, } static int hook_move_mount(const struct path *const from_path, - const struct path *const to_path) + const struct path *const to_path) { if (!landlock_get_current_domain()) return 0; @@ -502,7 +1075,7 @@ static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts) * view of the filesystem. */ static int hook_sb_pivotroot(const struct path *const old_path, - const struct path *const new_path) + const struct path *const new_path) { if (!landlock_get_current_domain()) return 0; @@ -511,97 +1084,34 @@ static int hook_sb_pivotroot(const struct path *const old_path, /* Path hooks */ -static inline u32 get_mode_access(const umode_t mode) -{ - switch (mode & S_IFMT) { - case S_IFLNK: - return LANDLOCK_ACCESS_FS_MAKE_SYM; - case 0: - /* A zero mode translates to S_IFREG. */ - case S_IFREG: - return LANDLOCK_ACCESS_FS_MAKE_REG; - case S_IFDIR: - return LANDLOCK_ACCESS_FS_MAKE_DIR; - case S_IFCHR: - return LANDLOCK_ACCESS_FS_MAKE_CHAR; - case S_IFBLK: - return LANDLOCK_ACCESS_FS_MAKE_BLOCK; - case S_IFIFO: - return LANDLOCK_ACCESS_FS_MAKE_FIFO; - case S_IFSOCK: - return LANDLOCK_ACCESS_FS_MAKE_SOCK; - default: - WARN_ON_ONCE(1); - return 0; - } -} - -/* - * Creating multiple links or renaming may lead to privilege escalations if not - * handled properly. Indeed, we must be sure that the source doesn't gain more - * privileges by being accessible from the destination. This is getting more - * complex when dealing with multiple layers. The whole picture can be seen as - * a multilayer partial ordering problem. A future version of Landlock will - * deal with that. - */ static int hook_path_link(struct dentry *const old_dentry, - const struct path *const new_dir, - struct dentry *const new_dentry) + const struct path *const new_dir, + struct dentry *const new_dentry) { - const struct landlock_ruleset *const dom = - landlock_get_current_domain(); - - if (!dom) - return 0; - /* The mount points are the same for old and new paths, cf. EXDEV. */ - if (old_dentry->d_parent != new_dir->dentry) - /* Gracefully forbids reparenting. */ - return -EXDEV; - if (unlikely(d_is_negative(old_dentry))) - return -ENOENT; - return check_access_path(dom, new_dir, - get_mode_access(d_backing_inode(old_dentry)->i_mode)); -} - -static inline u32 maybe_remove(const struct dentry *const dentry) -{ - if (d_is_negative(dentry)) - return 0; - return d_is_dir(dentry) ? LANDLOCK_ACCESS_FS_REMOVE_DIR : - LANDLOCK_ACCESS_FS_REMOVE_FILE; + return current_check_refer_path(old_dentry, new_dir, new_dentry, false, + false); } static int hook_path_rename(const struct path *const old_dir, - struct dentry *const old_dentry, - const struct path *const new_dir, - struct dentry *const new_dentry) + struct dentry *const old_dentry, + const struct path *const new_dir, + struct dentry *const new_dentry, + const unsigned int flags) { - const struct landlock_ruleset *const dom = - landlock_get_current_domain(); - - if (!dom) - return 0; - /* The mount points are the same for old and new paths, cf. EXDEV. */ - if (old_dir->dentry != new_dir->dentry) - /* Gracefully forbids reparenting. */ - return -EXDEV; - if (unlikely(d_is_negative(old_dentry))) - return -ENOENT; - /* RENAME_EXCHANGE is handled because directories are the same. */ - return check_access_path(dom, old_dir, maybe_remove(old_dentry) | - maybe_remove(new_dentry) | - get_mode_access(d_backing_inode(old_dentry)->i_mode)); + /* old_dir refers to old_dentry->d_parent and new_dir->mnt */ + return current_check_refer_path(old_dentry, new_dir, new_dentry, true, + !!(flags & RENAME_EXCHANGE)); } static int hook_path_mkdir(const struct path *const dir, - struct dentry *const dentry, const umode_t mode) + struct dentry *const dentry, const umode_t mode) { return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_DIR); } static int hook_path_mknod(const struct path *const dir, - struct dentry *const dentry, const umode_t mode, - const unsigned int dev) + struct dentry *const dentry, const umode_t mode, + const unsigned int dev) { const struct landlock_ruleset *const dom = landlock_get_current_domain(); @@ -612,28 +1122,29 @@ static int hook_path_mknod(const struct path *const dir, } static int hook_path_symlink(const struct path *const dir, - struct dentry *const dentry, const char *const old_name) + struct dentry *const dentry, + const char *const old_name) { return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_SYM); } static int hook_path_unlink(const struct path *const dir, - struct dentry *const dentry) + struct dentry *const dentry) { return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_FILE); } static int hook_path_rmdir(const struct path *const dir, - struct dentry *const dentry) + struct dentry *const dentry) { return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR); } /* File hooks */ -static inline u32 get_file_access(const struct file *const file) +static inline access_mask_t get_file_access(const struct file *const file) { - u32 access = 0; + access_mask_t access = 0; if (file->f_mode & FMODE_READ) { /* A directory can only be opened in read mode. */ @@ -688,5 +1199,5 @@ static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = { __init void landlock_add_fs_hooks(void) { security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), - LANDLOCK_NAME); + LANDLOCK_NAME); } diff --git a/security/landlock/fs.h b/security/landlock/fs.h index 187284b421c9..8db7acf9109b 100644 --- a/security/landlock/fs.h +++ b/security/landlock/fs.h @@ -50,14 +50,14 @@ struct landlock_superblock_security { atomic_long_t inode_refs; }; -static inline struct landlock_inode_security *landlock_inode( - const struct inode *const inode) +static inline struct landlock_inode_security * +landlock_inode(const struct inode *const inode) { return inode->i_security + landlock_blob_sizes.lbs_inode; } -static inline struct landlock_superblock_security *landlock_superblock( - const struct super_block *const superblock) +static inline struct landlock_superblock_security * +landlock_superblock(const struct super_block *const superblock) { return superblock->s_security + landlock_blob_sizes.lbs_superblock; } @@ -65,6 +65,7 @@ static inline struct landlock_superblock_security *landlock_superblock( __init void landlock_add_fs_hooks(void); int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, - const struct path *const path, u32 access_hierarchy); + const struct path *const path, + access_mask_t access_hierarchy); #endif /* _SECURITY_LANDLOCK_FS_H */ diff --git a/security/landlock/limits.h b/security/landlock/limits.h index 2a0a1095ee27..b54184ab9439 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -9,13 +9,19 @@ #ifndef _SECURITY_LANDLOCK_LIMITS_H #define _SECURITY_LANDLOCK_LIMITS_H +#include #include #include -#define LANDLOCK_MAX_NUM_LAYERS 64 +/* clang-format off */ + +#define LANDLOCK_MAX_NUM_LAYERS 16 #define LANDLOCK_MAX_NUM_RULES U32_MAX -#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_MAKE_SYM +#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_REFER #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1) +#define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS) + +/* clang-format on */ #endif /* _SECURITY_LANDLOCK_LIMITS_H */ diff --git a/security/landlock/object.c b/security/landlock/object.c index d674fdf9ff04..1f50612f0185 100644 --- a/security/landlock/object.c +++ b/security/landlock/object.c @@ -17,9 +17,9 @@ #include "object.h" -struct landlock_object *landlock_create_object( - const struct landlock_object_underops *const underops, - void *const underobj) +struct landlock_object * +landlock_create_object(const struct landlock_object_underops *const underops, + void *const underobj) { struct landlock_object *new_object; diff --git a/security/landlock/object.h b/security/landlock/object.h index 3f80674c6c8d..5f28c35e8aa8 100644 --- a/security/landlock/object.h +++ b/security/landlock/object.h @@ -76,9 +76,9 @@ struct landlock_object { }; }; -struct landlock_object *landlock_create_object( - const struct landlock_object_underops *const underops, - void *const underobj); +struct landlock_object * +landlock_create_object(const struct landlock_object_underops *const underops, + void *const underobj); void landlock_put_object(struct landlock_object *const object); diff --git a/security/landlock/ptrace.c b/security/landlock/ptrace.c index f55b82446de2..4c5b9cd71286 100644 --- a/security/landlock/ptrace.c +++ b/security/landlock/ptrace.c @@ -30,7 +30,7 @@ * means a subset of) the @child domain. */ static bool domain_scope_le(const struct landlock_ruleset *const parent, - const struct landlock_ruleset *const child) + const struct landlock_ruleset *const child) { const struct landlock_hierarchy *walker; @@ -48,7 +48,7 @@ static bool domain_scope_le(const struct landlock_ruleset *const parent, } static bool task_is_scoped(const struct task_struct *const parent, - const struct task_struct *const child) + const struct task_struct *const child) { bool is_scoped; const struct landlock_ruleset *dom_parent, *dom_child; @@ -62,7 +62,7 @@ static bool task_is_scoped(const struct task_struct *const parent, } static int task_ptrace(const struct task_struct *const parent, - const struct task_struct *const child) + const struct task_struct *const child) { /* Quick return for non-landlocked tasks. */ if (!landlocked(parent)) @@ -86,7 +86,7 @@ static int task_ptrace(const struct task_struct *const parent, * granted, -errno if denied. */ static int hook_ptrace_access_check(struct task_struct *const child, - const unsigned int mode) + const unsigned int mode) { return task_ptrace(current, child); } @@ -116,5 +116,5 @@ static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = { __init void landlock_add_ptrace_hooks(void) { security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), - LANDLOCK_NAME); + LANDLOCK_NAME); } diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index ec72b9262bf3..996484f98bfd 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -28,8 +28,9 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers) { struct landlock_ruleset *new_ruleset; - new_ruleset = kzalloc(struct_size(new_ruleset, fs_access_masks, - num_layers), GFP_KERNEL_ACCOUNT); + new_ruleset = + kzalloc(struct_size(new_ruleset, fs_access_masks, num_layers), + GFP_KERNEL_ACCOUNT); if (!new_ruleset) return ERR_PTR(-ENOMEM); refcount_set(&new_ruleset->usage, 1); @@ -44,7 +45,8 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers) return new_ruleset; } -struct landlock_ruleset *landlock_create_ruleset(const u32 fs_access_mask) +struct landlock_ruleset * +landlock_create_ruleset(const access_mask_t fs_access_mask) { struct landlock_ruleset *new_ruleset; @@ -66,11 +68,10 @@ static void build_check_rule(void) BUILD_BUG_ON(rule.num_layers < LANDLOCK_MAX_NUM_LAYERS); } -static struct landlock_rule *create_rule( - struct landlock_object *const object, - const struct landlock_layer (*const layers)[], - const u32 num_layers, - const struct landlock_layer *const new_layer) +static struct landlock_rule * +create_rule(struct landlock_object *const object, + const struct landlock_layer (*const layers)[], const u32 num_layers, + const struct landlock_layer *const new_layer) { struct landlock_rule *new_rule; u32 new_num_layers; @@ -85,7 +86,7 @@ static struct landlock_rule *create_rule( new_num_layers = num_layers; } new_rule = kzalloc(struct_size(new_rule, layers, new_num_layers), - GFP_KERNEL_ACCOUNT); + GFP_KERNEL_ACCOUNT); if (!new_rule) return ERR_PTR(-ENOMEM); RB_CLEAR_NODE(&new_rule->node); @@ -94,7 +95,7 @@ static struct landlock_rule *create_rule( new_rule->num_layers = new_num_layers; /* Copies the original layer stack. */ memcpy(new_rule->layers, layers, - flex_array_size(new_rule, layers, num_layers)); + flex_array_size(new_rule, layers, num_layers)); if (new_layer) /* Adds a copy of @new_layer on the layer stack. */ new_rule->layers[new_rule->num_layers - 1] = *new_layer; @@ -142,9 +143,9 @@ static void build_check_ruleset(void) * access rights. */ static int insert_rule(struct landlock_ruleset *const ruleset, - struct landlock_object *const object, - const struct landlock_layer (*const layers)[], - size_t num_layers) + struct landlock_object *const object, + const struct landlock_layer (*const layers)[], + size_t num_layers) { struct rb_node **walker_node; struct rb_node *parent_node = NULL; @@ -156,8 +157,8 @@ static int insert_rule(struct landlock_ruleset *const ruleset, return -ENOENT; walker_node = &(ruleset->root.rb_node); while (*walker_node) { - struct landlock_rule *const this = rb_entry(*walker_node, - struct landlock_rule, node); + struct landlock_rule *const this = + rb_entry(*walker_node, struct landlock_rule, node); if (this->object != object) { parent_node = *walker_node; @@ -194,7 +195,7 @@ static int insert_rule(struct landlock_ruleset *const ruleset, * ruleset and a domain. */ new_rule = create_rule(object, &this->layers, this->num_layers, - &(*layers)[0]); + &(*layers)[0]); if (IS_ERR(new_rule)) return PTR_ERR(new_rule); rb_replace_node(&this->node, &new_rule->node, &ruleset->root); @@ -228,13 +229,14 @@ static void build_check_layer(void) /* @ruleset must be locked by the caller. */ int landlock_insert_rule(struct landlock_ruleset *const ruleset, - struct landlock_object *const object, const u32 access) + struct landlock_object *const object, + const access_mask_t access) { - struct landlock_layer layers[] = {{ + struct landlock_layer layers[] = { { .access = access, /* When @level is zero, insert_rule() extends @ruleset. */ .level = 0, - }}; + } }; build_check_layer(); return insert_rule(ruleset, object, &layers, ARRAY_SIZE(layers)); @@ -257,7 +259,7 @@ static void put_hierarchy(struct landlock_hierarchy *hierarchy) } static int merge_ruleset(struct landlock_ruleset *const dst, - struct landlock_ruleset *const src) + struct landlock_ruleset *const src) { struct landlock_rule *walker_rule, *next_rule; int err = 0; @@ -282,11 +284,11 @@ static int merge_ruleset(struct landlock_ruleset *const dst, dst->fs_access_masks[dst->num_layers - 1] = src->fs_access_masks[0]; /* Merges the @src tree. */ - rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, - &src->root, node) { - struct landlock_layer layers[] = {{ + rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, &src->root, + node) { + struct landlock_layer layers[] = { { .level = dst->num_layers, - }}; + } }; if (WARN_ON_ONCE(walker_rule->num_layers != 1)) { err = -EINVAL; @@ -298,7 +300,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst, } layers[0].access = walker_rule->layers[0].access; err = insert_rule(dst, walker_rule->object, &layers, - ARRAY_SIZE(layers)); + ARRAY_SIZE(layers)); if (err) goto out_unlock; } @@ -310,7 +312,7 @@ out_unlock: } static int inherit_ruleset(struct landlock_ruleset *const parent, - struct landlock_ruleset *const child) + struct landlock_ruleset *const child) { struct landlock_rule *walker_rule, *next_rule; int err = 0; @@ -325,9 +327,10 @@ static int inherit_ruleset(struct landlock_ruleset *const parent, /* Copies the @parent tree. */ rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, - &parent->root, node) { + &parent->root, node) { err = insert_rule(child, walker_rule->object, - &walker_rule->layers, walker_rule->num_layers); + &walker_rule->layers, + walker_rule->num_layers); if (err) goto out_unlock; } @@ -338,7 +341,7 @@ static int inherit_ruleset(struct landlock_ruleset *const parent, } /* Copies the parent layer stack and leaves a space for the new layer. */ memcpy(child->fs_access_masks, parent->fs_access_masks, - flex_array_size(parent, fs_access_masks, parent->num_layers)); + flex_array_size(parent, fs_access_masks, parent->num_layers)); if (WARN_ON_ONCE(!parent->hierarchy)) { err = -EINVAL; @@ -358,8 +361,7 @@ static void free_ruleset(struct landlock_ruleset *const ruleset) struct landlock_rule *freeme, *next; might_sleep(); - rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root, - node) + rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root, node) free_rule(freeme); put_hierarchy(ruleset->hierarchy); kfree(ruleset); @@ -397,9 +399,9 @@ void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset) * Returns the intersection of @parent and @ruleset, or returns @parent if * @ruleset is empty, or returns a duplicate of @ruleset if @parent is empty. */ -struct landlock_ruleset *landlock_merge_ruleset( - struct landlock_ruleset *const parent, - struct landlock_ruleset *const ruleset) +struct landlock_ruleset * +landlock_merge_ruleset(struct landlock_ruleset *const parent, + struct landlock_ruleset *const ruleset) { struct landlock_ruleset *new_dom; u32 num_layers; @@ -421,8 +423,8 @@ struct landlock_ruleset *landlock_merge_ruleset( new_dom = create_ruleset(num_layers); if (IS_ERR(new_dom)) return new_dom; - new_dom->hierarchy = kzalloc(sizeof(*new_dom->hierarchy), - GFP_KERNEL_ACCOUNT); + new_dom->hierarchy = + kzalloc(sizeof(*new_dom->hierarchy), GFP_KERNEL_ACCOUNT); if (!new_dom->hierarchy) { err = -ENOMEM; goto out_put_dom; @@ -449,9 +451,9 @@ out_put_dom: /* * The returned access has the same lifetime as @ruleset. */ -const struct landlock_rule *landlock_find_rule( - const struct landlock_ruleset *const ruleset, - const struct landlock_object *const object) +const struct landlock_rule * +landlock_find_rule(const struct landlock_ruleset *const ruleset, + const struct landlock_object *const object) { const struct rb_node *node; @@ -459,8 +461,8 @@ const struct landlock_rule *landlock_find_rule( return NULL; node = ruleset->root.rb_node; while (node) { - struct landlock_rule *this = rb_entry(node, - struct landlock_rule, node); + struct landlock_rule *this = + rb_entry(node, struct landlock_rule, node); if (this->object == object) return this; diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index 2d3ed7ec5a0a..d43231b783e4 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -9,13 +9,26 @@ #ifndef _SECURITY_LANDLOCK_RULESET_H #define _SECURITY_LANDLOCK_RULESET_H +#include +#include #include #include #include #include +#include "limits.h" #include "object.h" +typedef u16 access_mask_t; +/* Makes sure all filesystem access rights can be stored. */ +static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS); +/* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */ +static_assert(sizeof(unsigned long) >= sizeof(access_mask_t)); + +typedef u16 layer_mask_t; +/* Makes sure all layers can be checked. */ +static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS); + /** * struct landlock_layer - Access rights for a given layer */ @@ -28,7 +41,7 @@ struct landlock_layer { * @access: Bitfield of allowed actions on the kernel object. They are * relative to the object type (e.g. %LANDLOCK_ACTION_FS_READ). */ - u16 access; + access_mask_t access; }; /** @@ -135,26 +148,28 @@ struct landlock_ruleset { * layers are set once and never changed for the * lifetime of the ruleset. */ - u16 fs_access_masks[]; + access_mask_t fs_access_masks[]; }; }; }; -struct landlock_ruleset *landlock_create_ruleset(const u32 fs_access_mask); +struct landlock_ruleset * +landlock_create_ruleset(const access_mask_t fs_access_mask); void landlock_put_ruleset(struct landlock_ruleset *const ruleset); void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset); int landlock_insert_rule(struct landlock_ruleset *const ruleset, - struct landlock_object *const object, const u32 access); + struct landlock_object *const object, + const access_mask_t access); -struct landlock_ruleset *landlock_merge_ruleset( - struct landlock_ruleset *const parent, - struct landlock_ruleset *const ruleset); +struct landlock_ruleset * +landlock_merge_ruleset(struct landlock_ruleset *const parent, + struct landlock_ruleset *const ruleset); -const struct landlock_rule *landlock_find_rule( - const struct landlock_ruleset *const ruleset, - const struct landlock_object *const object); +const struct landlock_rule * +landlock_find_rule(const struct landlock_ruleset *const ruleset, + const struct landlock_object *const object); static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset) { diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 7e27ce394020..735a0865ea11 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -43,9 +43,10 @@ * @src: User space pointer or NULL. * @usize: (Alleged) size of the data pointed to by @src. */ -static __always_inline int copy_min_struct_from_user(void *const dst, - const size_t ksize, const size_t ksize_min, - const void __user *const src, const size_t usize) +static __always_inline int +copy_min_struct_from_user(void *const dst, const size_t ksize, + const size_t ksize_min, const void __user *const src, + const size_t usize) { /* Checks buffer inconsistencies. */ BUILD_BUG_ON(!dst); @@ -93,7 +94,7 @@ static void build_check_abi(void) /* Ruleset handling */ static int fop_ruleset_release(struct inode *const inode, - struct file *const filp) + struct file *const filp) { struct landlock_ruleset *ruleset = filp->private_data; @@ -102,15 +103,15 @@ static int fop_ruleset_release(struct inode *const inode, } static ssize_t fop_dummy_read(struct file *const filp, char __user *const buf, - const size_t size, loff_t *const ppos) + const size_t size, loff_t *const ppos) { /* Dummy handler to enable FMODE_CAN_READ. */ return -EINVAL; } static ssize_t fop_dummy_write(struct file *const filp, - const char __user *const buf, const size_t size, - loff_t *const ppos) + const char __user *const buf, const size_t size, + loff_t *const ppos) { /* Dummy handler to enable FMODE_CAN_WRITE. */ return -EINVAL; @@ -128,7 +129,7 @@ static const struct file_operations ruleset_fops = { .write = fop_dummy_write, }; -#define LANDLOCK_ABI_VERSION 1 +#define LANDLOCK_ABI_VERSION 2 /** * sys_landlock_create_ruleset - Create a new ruleset @@ -168,22 +169,23 @@ SYSCALL_DEFINE3(landlock_create_ruleset, return -EOPNOTSUPP; if (flags) { - if ((flags == LANDLOCK_CREATE_RULESET_VERSION) - && !attr && !size) + if ((flags == LANDLOCK_CREATE_RULESET_VERSION) && !attr && + !size) return LANDLOCK_ABI_VERSION; return -EINVAL; } /* Copies raw user space buffer. */ err = copy_min_struct_from_user(&ruleset_attr, sizeof(ruleset_attr), - offsetofend(typeof(ruleset_attr), handled_access_fs), - attr, size); + offsetofend(typeof(ruleset_attr), + handled_access_fs), + attr, size); if (err) return err; /* Checks content (and 32-bits cast). */ if ((ruleset_attr.handled_access_fs | LANDLOCK_MASK_ACCESS_FS) != - LANDLOCK_MASK_ACCESS_FS) + LANDLOCK_MASK_ACCESS_FS) return -EINVAL; /* Checks arguments and transforms to kernel struct. */ @@ -193,7 +195,7 @@ SYSCALL_DEFINE3(landlock_create_ruleset, /* Creates anonymous FD referring to the ruleset. */ ruleset_fd = anon_inode_getfd("[landlock-ruleset]", &ruleset_fops, - ruleset, O_RDWR | O_CLOEXEC); + ruleset, O_RDWR | O_CLOEXEC); if (ruleset_fd < 0) landlock_put_ruleset(ruleset); return ruleset_fd; @@ -204,7 +206,7 @@ SYSCALL_DEFINE3(landlock_create_ruleset, * landlock_put_ruleset() on the return value. */ static struct landlock_ruleset *get_ruleset_from_fd(const int fd, - const fmode_t mode) + const fmode_t mode) { struct fd ruleset_f; struct landlock_ruleset *ruleset; @@ -244,8 +246,8 @@ static int get_path_from_fd(const s32 fd, struct path *const path) struct fd f; int err = 0; - BUILD_BUG_ON(!__same_type(fd, - ((struct landlock_path_beneath_attr *)NULL)->parent_fd)); + BUILD_BUG_ON(!__same_type( + fd, ((struct landlock_path_beneath_attr *)NULL)->parent_fd)); /* Handles O_PATH. */ f = fdget_raw(fd); @@ -257,10 +259,10 @@ static int get_path_from_fd(const s32 fd, struct path *const path) * pipefs). */ if ((f.file->f_op == &ruleset_fops) || - (f.file->f_path.mnt->mnt_flags & MNT_INTERNAL) || - (f.file->f_path.dentry->d_sb->s_flags & SB_NOUSER) || - d_is_negative(f.file->f_path.dentry) || - IS_PRIVATE(d_backing_inode(f.file->f_path.dentry))) { + (f.file->f_path.mnt->mnt_flags & MNT_INTERNAL) || + (f.file->f_path.dentry->d_sb->s_flags & SB_NOUSER) || + d_is_negative(f.file->f_path.dentry) || + IS_PRIVATE(d_backing_inode(f.file->f_path.dentry))) { err = -EBADFD; goto out_fdput; } @@ -290,19 +292,18 @@ out_fdput: * * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; * - EINVAL: @flags is not 0, or inconsistent access in the rule (i.e. - * &landlock_path_beneath_attr.allowed_access is not a subset of the rule's - * accesses); + * &landlock_path_beneath_attr.allowed_access is not a subset of the + * ruleset handled accesses); * - ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access); * - EBADF: @ruleset_fd is not a file descriptor for the current thread, or a * member of @rule_attr is not a file descriptor as expected; * - EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of - * @rule_attr is not the expected file descriptor type (e.g. file open - * without O_PATH); + * @rule_attr is not the expected file descriptor type; * - EPERM: @ruleset_fd has no write access to the underlying ruleset; * - EFAULT: @rule_attr inconsistency. */ -SYSCALL_DEFINE4(landlock_add_rule, - const int, ruleset_fd, const enum landlock_rule_type, rule_type, +SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, + const enum landlock_rule_type, rule_type, const void __user *const, rule_attr, const __u32, flags) { struct landlock_path_beneath_attr path_beneath_attr; @@ -317,20 +318,24 @@ SYSCALL_DEFINE4(landlock_add_rule, if (flags) return -EINVAL; - if (rule_type != LANDLOCK_RULE_PATH_BENEATH) - return -EINVAL; - - /* Copies raw user space buffer, only one type for now. */ - res = copy_from_user(&path_beneath_attr, rule_attr, - sizeof(path_beneath_attr)); - if (res) - return -EFAULT; - /* Gets and checks the ruleset. */ ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE); if (IS_ERR(ruleset)) return PTR_ERR(ruleset); + if (rule_type != LANDLOCK_RULE_PATH_BENEATH) { + err = -EINVAL; + goto out_put_ruleset; + } + + /* Copies raw user space buffer, only one type for now. */ + res = copy_from_user(&path_beneath_attr, rule_attr, + sizeof(path_beneath_attr)); + if (res) { + err = -EFAULT; + goto out_put_ruleset; + } + /* * Informs about useless rule: empty allowed_access (i.e. deny rules) * are ignored in path walks. @@ -344,7 +349,7 @@ SYSCALL_DEFINE4(landlock_add_rule, * (ruleset->fs_access_masks[0] is automatically upgraded to 64-bits). */ if ((path_beneath_attr.allowed_access | ruleset->fs_access_masks[0]) != - ruleset->fs_access_masks[0]) { + ruleset->fs_access_masks[0]) { err = -EINVAL; goto out_put_ruleset; } @@ -356,7 +361,7 @@ SYSCALL_DEFINE4(landlock_add_rule, /* Imports the new rule. */ err = landlock_append_fs_rule(ruleset, &path, - path_beneath_attr.allowed_access); + path_beneath_attr.allowed_access); path_put(&path); out_put_ruleset: @@ -389,8 +394,8 @@ out_put_ruleset: * - E2BIG: The maximum number of stacked rulesets is reached for the current * thread. */ -SYSCALL_DEFINE2(landlock_restrict_self, - const int, ruleset_fd, const __u32, flags) +SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32, + flags) { struct landlock_ruleset *new_dom, *ruleset; struct cred *new_cred; @@ -400,18 +405,18 @@ SYSCALL_DEFINE2(landlock_restrict_self, if (!landlock_initialized) return -EOPNOTSUPP; - /* No flag for now. */ - if (flags) - return -EINVAL; - /* * Similar checks as for seccomp(2), except that an -EPERM may be * returned. */ if (!task_no_new_privs(current) && - !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) + !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) return -EPERM; + /* No flag for now. */ + if (flags) + return -EINVAL; + /* Gets and checks the ruleset. */ ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_READ); if (IS_ERR(ruleset)) diff --git a/security/security.c b/security/security.c index 99956d2d29e1..188b8f782220 100644 --- a/security/security.c +++ b/security/security.c @@ -1198,15 +1198,8 @@ int security_path_rename(const struct path *old_dir, struct dentry *old_dentry, (d_is_positive(new_dentry) && IS_PRIVATE(d_backing_inode(new_dentry))))) return 0; - if (flags & RENAME_EXCHANGE) { - int err = call_int_hook(path_rename, 0, new_dir, new_dentry, - old_dir, old_dentry); - if (err) - return err; - } - return call_int_hook(path_rename, 0, old_dir, old_dentry, new_dir, - new_dentry); + new_dentry, flags); } EXPORT_SYMBOL(security_path_rename); diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 658eab05599e..9e61014073cc 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -1192,7 +1192,6 @@ static ssize_t smk_write_net4addr(struct file *file, const char __user *buf, rc = -EINVAL; goto free_out; } - m = BEBITS; masks = 32; } if (masks > BEBITS) { diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c index b6a31901f289..71e82d855ebf 100644 --- a/security/tomoyo/tomoyo.c +++ b/security/tomoyo/tomoyo.c @@ -264,17 +264,26 @@ static int tomoyo_path_link(struct dentry *old_dentry, const struct path *new_di * @old_dentry: Pointer to "struct dentry". * @new_parent: Pointer to "struct path". * @new_dentry: Pointer to "struct dentry". + * @flags: Rename options. * * Returns 0 on success, negative value otherwise. */ static int tomoyo_path_rename(const struct path *old_parent, struct dentry *old_dentry, const struct path *new_parent, - struct dentry *new_dentry) + struct dentry *new_dentry, + const unsigned int flags) { struct path path1 = { .mnt = old_parent->mnt, .dentry = old_dentry }; struct path path2 = { .mnt = new_parent->mnt, .dentry = new_dentry }; + if (flags & RENAME_EXCHANGE) { + const int err = tomoyo_path2_perm(TOMOYO_TYPE_RENAME, &path2, + &path1); + + if (err) + return err; + } return tomoyo_path2_perm(TOMOYO_TYPE_RENAME, &path1, &path2); } diff --git a/tools/certs/print-cert-tbs-hash.sh b/tools/certs/print-cert-tbs-hash.sh new file mode 100755 index 000000000000..c93df5387ec9 --- /dev/null +++ b/tools/certs/print-cert-tbs-hash.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright © 2020, Microsoft Corporation. All rights reserved. +# +# Author: Mickaël Salaün +# +# Compute and print the To Be Signed (TBS) hash of a certificate. This is used +# as description of keys in the blacklist keyring to identify certificates. +# This output should be redirected, without newline, in a file (hash0.txt) and +# signed to create a PKCS#7 file (hash0.p7s). Both of these files can then be +# loaded in the kernel with. +# +# Exemple on a workstation: +# ./print-cert-tbs-hash.sh certificate-to-invalidate.pem > hash0.txt +# openssl smime -sign -in hash0.txt -inkey builtin-private-key.pem \ +# -signer builtin-certificate.pem -certfile certificate-chain.pem \ +# -noattr -binary -outform DER -out hash0.p7s +# +# Exemple on a managed system: +# keyctl padd blacklist "$(< hash0.txt)" %:.blacklist < hash0.p7s + +set -u -e -o pipefail + +CERT="${1:-}" +BASENAME="$(basename -- "${BASH_SOURCE[0]}")" + +if [ $# -ne 1 ] || [ ! -f "${CERT}" ]; then + echo "usage: ${BASENAME} " >&2 + exit 1 +fi + +# Checks that it is indeed a certificate (PEM or DER encoded) and exclude the +# optional PEM text header. +if ! PEM="$(openssl x509 -inform DER -in "${CERT}" 2>/dev/null || openssl x509 -in "${CERT}")"; then + echo "ERROR: Failed to parse certificate" >&2 + exit 1 +fi + +# TBSCertificate starts at the second entry. +# Cf. https://tools.ietf.org/html/rfc3280#section-4.1 +# +# Exemple of first lines printed by openssl asn1parse: +# 0:d=0 hl=4 l= 763 cons: SEQUENCE +# 4:d=1 hl=4 l= 483 cons: SEQUENCE +# 8:d=2 hl=2 l= 3 cons: cont [ 0 ] +# 10:d=3 hl=2 l= 1 prim: INTEGER :02 +# 13:d=2 hl=2 l= 20 prim: INTEGER :3CEB2CB8818D968AC00EEFE195F0DF9665328B7B +# 35:d=2 hl=2 l= 13 cons: SEQUENCE +# 37:d=3 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption +RANGE_AND_DIGEST_RE=' +2s/^\s*\([0-9]\+\):d=\s*[0-9]\+\s\+hl=\s*[0-9]\+\s\+l=\s*\([0-9]\+\)\s\+cons:\s*SEQUENCE\s*$/\1 \2/p; +7s/^\s*[0-9]\+:d=\s*[0-9]\+\s\+hl=\s*[0-9]\+\s\+l=\s*[0-9]\+\s\+prim:\s*OBJECT\s*:\(.*\)$/\1/p; +' + +RANGE_AND_DIGEST=($(echo "${PEM}" | \ + openssl asn1parse -in - | \ + sed -n -e "${RANGE_AND_DIGEST_RE}")) + +if [ "${#RANGE_AND_DIGEST[@]}" != 3 ]; then + echo "ERROR: Failed to parse TBSCertificate." >&2 + exit 1 +fi + +OFFSET="${RANGE_AND_DIGEST[0]}" +END="$(( OFFSET + RANGE_AND_DIGEST[1] ))" +DIGEST="${RANGE_AND_DIGEST[2]}" + +# The signature hash algorithm is used by Linux to blacklist certificates. +# Cf. crypto/asymmetric_keys/x509_cert_parser.c:x509_note_pkey_algo() +DIGEST_MATCH="" +while read -r DIGEST_ITEM; do + if [ -z "${DIGEST_ITEM}" ]; then + break + fi + if echo "${DIGEST}" | grep -qiF "${DIGEST_ITEM}"; then + DIGEST_MATCH="${DIGEST_ITEM}" + break + fi +done < <(openssl list -digest-commands | tr ' ' '\n' | sort -ur) + +if [ -z "${DIGEST_MATCH}" ]; then + echo "ERROR: Unknown digest algorithm: ${DIGEST}" >&2 + exit 1 +fi + +echo "${PEM}" | \ + openssl x509 -in - -outform DER | \ + dd "bs=1" "skip=${OFFSET}" "count=${END}" "status=none" | \ + openssl dgst "-${DIGEST_MATCH}" - | \ + awk '{printf "tbs:" $2}' diff --git a/tools/power/acpi/common/cmfsize.c b/tools/power/acpi/common/cmfsize.c index 185b8c588e1d..38f9b9da8170 100644 --- a/tools/power/acpi/common/cmfsize.c +++ b/tools/power/acpi/common/cmfsize.c @@ -3,7 +3,7 @@ * * Module Name: cmfsize - Common get file size function * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/common/getopt.c b/tools/power/acpi/common/getopt.c index 3c265bc917a1..96fd6cec78e2 100644 --- a/tools/power/acpi/common/getopt.c +++ b/tools/power/acpi/common/getopt.c @@ -3,7 +3,7 @@ * * Module Name: getopt * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/os_specific/service_layers/oslinuxtbl.c b/tools/power/acpi/os_specific/service_layers/oslinuxtbl.c index ccabdbaae6a4..bd08f36df4a7 100644 --- a/tools/power/acpi/os_specific/service_layers/oslinuxtbl.c +++ b/tools/power/acpi/os_specific/service_layers/oslinuxtbl.c @@ -3,7 +3,7 @@ * * Module Name: oslinuxtbl - Linux OSL for obtaining ACPI tables * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/os_specific/service_layers/osunixdir.c b/tools/power/acpi/os_specific/service_layers/osunixdir.c index edd99274cd12..5107892d054b 100644 --- a/tools/power/acpi/os_specific/service_layers/osunixdir.c +++ b/tools/power/acpi/os_specific/service_layers/osunixdir.c @@ -3,7 +3,7 @@ * * Module Name: osunixdir - Unix directory access interfaces * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/os_specific/service_layers/osunixmap.c b/tools/power/acpi/os_specific/service_layers/osunixmap.c index fee0022560d5..6ff4edd8dc3b 100644 --- a/tools/power/acpi/os_specific/service_layers/osunixmap.c +++ b/tools/power/acpi/os_specific/service_layers/osunixmap.c @@ -3,7 +3,7 @@ * * Module Name: osunixmap - Unix OSL for file mappings * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/os_specific/service_layers/osunixxf.c b/tools/power/acpi/os_specific/service_layers/osunixxf.c index 0861728da562..b3651a04d68c 100644 --- a/tools/power/acpi/os_specific/service_layers/osunixxf.c +++ b/tools/power/acpi/os_specific/service_layers/osunixxf.c @@ -3,7 +3,7 @@ * * Module Name: osunixxf - UNIX OSL interfaces * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/tools/acpidump/acpidump.h b/tools/power/acpi/tools/acpidump/acpidump.h index e0ebc1dab1cc..153249c87fd7 100644 --- a/tools/power/acpi/tools/acpidump/acpidump.h +++ b/tools/power/acpi/tools/acpidump/acpidump.h @@ -3,7 +3,7 @@ * * Module Name: acpidump.h - Include file for acpi_dump utility * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/tools/acpidump/apdump.c b/tools/power/acpi/tools/acpidump/apdump.c index 444e3d78bd89..d54dde02b87d 100644 --- a/tools/power/acpi/tools/acpidump/apdump.c +++ b/tools/power/acpi/tools/acpidump/apdump.c @@ -3,7 +3,7 @@ * * Module Name: apdump - Dump routines for ACPI tables (acpidump) * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/tools/acpidump/apfiles.c b/tools/power/acpi/tools/acpidump/apfiles.c index da0c6e13042b..2d9b45a9b526 100644 --- a/tools/power/acpi/tools/acpidump/apfiles.c +++ b/tools/power/acpi/tools/acpidump/apfiles.c @@ -3,7 +3,7 @@ * * Module Name: apfiles - File-related functions for acpidump utility * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/tools/acpidump/apmain.c b/tools/power/acpi/tools/acpidump/apmain.c index a4cf6042fcfd..44b23fc53dd9 100644 --- a/tools/power/acpi/tools/acpidump/apmain.c +++ b/tools/power/acpi/tools/acpidump/apmain.c @@ -3,7 +3,7 @@ * * Module Name: apmain - Main module for the acpidump utility * - * Copyright (C) 2000 - 2021, Intel Corp. + * Copyright (C) 2000 - 2022, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/x86/turbostat/Makefile b/tools/power/x86/turbostat/Makefile index f3e3c94ab9bd..92e139b9c792 100644 --- a/tools/power/x86/turbostat/Makefile +++ b/tools/power/x86/turbostat/Makefile @@ -9,7 +9,7 @@ ifeq ("$(origin O)", "command line") endif turbostat : turbostat.c -override CFLAGS += -O2 -Wall -I../../../include +override CFLAGS += -O2 -Wall -Wextra -I../../../include override CFLAGS += -DMSRHEADER='"../../../../arch/x86/include/asm/msr-index.h"' override CFLAGS += -DINTEL_FAMILY_HEADER='"../../../../arch/x86/include/asm/intel-family.h"' override CFLAGS += -D_FILE_OFFSET_BITS=64 diff --git a/tools/power/x86/turbostat/turbostat.8 b/tools/power/x86/turbostat/turbostat.8 index 9b17097bc3d7..1e7d3de55a94 100644 --- a/tools/power/x86/turbostat/turbostat.8 +++ b/tools/power/x86/turbostat/turbostat.8 @@ -292,7 +292,7 @@ starts a new interval. must be run as root. Alternatively, non-root users can be enabled to run turbostat this way: -# setcap cap_sys_rawio=ep ./turbostat +# setcap cap_sys_admin,cap_sys_rawio,cap_sys_nice=+ep ./turbostat # chmod +r /dev/cpu/*/msr diff --git a/tools/power/x86/turbostat/turbostat.c b/tools/power/x86/turbostat/turbostat.c index bc5ae0872fed..ede31a4287a0 100644 --- a/tools/power/x86/turbostat/turbostat.c +++ b/tools/power/x86/turbostat/turbostat.c @@ -3,7 +3,7 @@ * turbostat -- show CPU frequency and C-state residency * on modern Intel and AMD processors. * - * Copyright (c) 2021 Intel Corporation. + * Copyright (c) 2022 Intel Corporation. * Len Brown */ @@ -37,6 +37,171 @@ #include #include +#define UNUSED(x) (void)(x) + +/* + * This list matches the column headers, except + * 1. built-in only, the sysfs counters are not here -- we learn of those at run-time + * 2. Core and CPU are moved to the end, we can't have strings that contain them + * matching on them for --show and --hide. + */ + +/* + * buffer size used by sscanf() for added column names + * Usually truncated to 7 characters, but also handles 18 columns for raw 64-bit counters + */ +#define NAME_BYTES 20 +#define PATH_BYTES 128 + +enum counter_scope { SCOPE_CPU, SCOPE_CORE, SCOPE_PACKAGE }; +enum counter_type { COUNTER_ITEMS, COUNTER_CYCLES, COUNTER_SECONDS, COUNTER_USEC }; +enum counter_format { FORMAT_RAW, FORMAT_DELTA, FORMAT_PERCENT }; + +struct msr_counter { + unsigned int msr_num; + char name[NAME_BYTES]; + char path[PATH_BYTES]; + unsigned int width; + enum counter_type type; + enum counter_format format; + struct msr_counter *next; + unsigned int flags; +#define FLAGS_HIDE (1 << 0) +#define FLAGS_SHOW (1 << 1) +#define SYSFS_PERCPU (1 << 1) +}; + +struct msr_counter bic[] = { + { 0x0, "usec", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Time_Of_Day_Seconds", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Package", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Node", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Avg_MHz", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Busy%", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Bzy_MHz", "", 0, 0, 0, NULL, 0 }, + { 0x0, "TSC_MHz", "", 0, 0, 0, NULL, 0 }, + { 0x0, "IRQ", "", 0, 0, 0, NULL, 0 }, + { 0x0, "SMI", "", 32, 0, FORMAT_DELTA, NULL, 0 }, + { 0x0, "sysfs", "", 0, 0, 0, NULL, 0 }, + { 0x0, "CPU%c1", "", 0, 0, 0, NULL, 0 }, + { 0x0, "CPU%c3", "", 0, 0, 0, NULL, 0 }, + { 0x0, "CPU%c6", "", 0, 0, 0, NULL, 0 }, + { 0x0, "CPU%c7", "", 0, 0, 0, NULL, 0 }, + { 0x0, "ThreadC", "", 0, 0, 0, NULL, 0 }, + { 0x0, "CoreTmp", "", 0, 0, 0, NULL, 0 }, + { 0x0, "CoreCnt", "", 0, 0, 0, NULL, 0 }, + { 0x0, "PkgTmp", "", 0, 0, 0, NULL, 0 }, + { 0x0, "GFX%rc6", "", 0, 0, 0, NULL, 0 }, + { 0x0, "GFXMHz", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Pkg%pc2", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Pkg%pc3", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Pkg%pc6", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Pkg%pc7", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Pkg%pc8", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Pkg%pc9", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Pk%pc10", "", 0, 0, 0, NULL, 0 }, + { 0x0, "CPU%LPI", "", 0, 0, 0, NULL, 0 }, + { 0x0, "SYS%LPI", "", 0, 0, 0, NULL, 0 }, + { 0x0, "PkgWatt", "", 0, 0, 0, NULL, 0 }, + { 0x0, "CorWatt", "", 0, 0, 0, NULL, 0 }, + { 0x0, "GFXWatt", "", 0, 0, 0, NULL, 0 }, + { 0x0, "PkgCnt", "", 0, 0, 0, NULL, 0 }, + { 0x0, "RAMWatt", "", 0, 0, 0, NULL, 0 }, + { 0x0, "PKG_%", "", 0, 0, 0, NULL, 0 }, + { 0x0, "RAM_%", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Pkg_J", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Cor_J", "", 0, 0, 0, NULL, 0 }, + { 0x0, "GFX_J", "", 0, 0, 0, NULL, 0 }, + { 0x0, "RAM_J", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Mod%c6", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Totl%C0", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Any%C0", "", 0, 0, 0, NULL, 0 }, + { 0x0, "GFX%C0", "", 0, 0, 0, NULL, 0 }, + { 0x0, "CPUGFX%", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Core", "", 0, 0, 0, NULL, 0 }, + { 0x0, "CPU", "", 0, 0, 0, NULL, 0 }, + { 0x0, "APIC", "", 0, 0, 0, NULL, 0 }, + { 0x0, "X2APIC", "", 0, 0, 0, NULL, 0 }, + { 0x0, "Die", "", 0, 0, 0, NULL, 0 }, + { 0x0, "GFXAMHz", "", 0, 0, 0, NULL, 0 }, + { 0x0, "IPC", "", 0, 0, 0, NULL, 0 }, + { 0x0, "CoreThr", "", 0, 0, 0, NULL, 0 }, +}; + +#define MAX_BIC (sizeof(bic) / sizeof(struct msr_counter)) +#define BIC_USEC (1ULL << 0) +#define BIC_TOD (1ULL << 1) +#define BIC_Package (1ULL << 2) +#define BIC_Node (1ULL << 3) +#define BIC_Avg_MHz (1ULL << 4) +#define BIC_Busy (1ULL << 5) +#define BIC_Bzy_MHz (1ULL << 6) +#define BIC_TSC_MHz (1ULL << 7) +#define BIC_IRQ (1ULL << 8) +#define BIC_SMI (1ULL << 9) +#define BIC_sysfs (1ULL << 10) +#define BIC_CPU_c1 (1ULL << 11) +#define BIC_CPU_c3 (1ULL << 12) +#define BIC_CPU_c6 (1ULL << 13) +#define BIC_CPU_c7 (1ULL << 14) +#define BIC_ThreadC (1ULL << 15) +#define BIC_CoreTmp (1ULL << 16) +#define BIC_CoreCnt (1ULL << 17) +#define BIC_PkgTmp (1ULL << 18) +#define BIC_GFX_rc6 (1ULL << 19) +#define BIC_GFXMHz (1ULL << 20) +#define BIC_Pkgpc2 (1ULL << 21) +#define BIC_Pkgpc3 (1ULL << 22) +#define BIC_Pkgpc6 (1ULL << 23) +#define BIC_Pkgpc7 (1ULL << 24) +#define BIC_Pkgpc8 (1ULL << 25) +#define BIC_Pkgpc9 (1ULL << 26) +#define BIC_Pkgpc10 (1ULL << 27) +#define BIC_CPU_LPI (1ULL << 28) +#define BIC_SYS_LPI (1ULL << 29) +#define BIC_PkgWatt (1ULL << 30) +#define BIC_CorWatt (1ULL << 31) +#define BIC_GFXWatt (1ULL << 32) +#define BIC_PkgCnt (1ULL << 33) +#define BIC_RAMWatt (1ULL << 34) +#define BIC_PKG__ (1ULL << 35) +#define BIC_RAM__ (1ULL << 36) +#define BIC_Pkg_J (1ULL << 37) +#define BIC_Cor_J (1ULL << 38) +#define BIC_GFX_J (1ULL << 39) +#define BIC_RAM_J (1ULL << 40) +#define BIC_Mod_c6 (1ULL << 41) +#define BIC_Totl_c0 (1ULL << 42) +#define BIC_Any_c0 (1ULL << 43) +#define BIC_GFX_c0 (1ULL << 44) +#define BIC_CPUGFX (1ULL << 45) +#define BIC_Core (1ULL << 46) +#define BIC_CPU (1ULL << 47) +#define BIC_APIC (1ULL << 48) +#define BIC_X2APIC (1ULL << 49) +#define BIC_Die (1ULL << 50) +#define BIC_GFXACTMHz (1ULL << 51) +#define BIC_IPC (1ULL << 52) +#define BIC_CORE_THROT_CNT (1ULL << 53) + +#define BIC_TOPOLOGY (BIC_Package | BIC_Node | BIC_CoreCnt | BIC_PkgCnt | BIC_Core | BIC_CPU | BIC_Die ) +#define BIC_THERMAL_PWR ( BIC_CoreTmp | BIC_PkgTmp | BIC_PkgWatt | BIC_CorWatt | BIC_GFXWatt | BIC_RAMWatt | BIC_PKG__ | BIC_RAM__) +#define BIC_FREQUENCY ( BIC_Avg_MHz | BIC_Busy | BIC_Bzy_MHz | BIC_TSC_MHz | BIC_GFXMHz | BIC_GFXACTMHz ) +#define BIC_IDLE ( BIC_sysfs | BIC_CPU_c1 | BIC_CPU_c3 | BIC_CPU_c6 | BIC_CPU_c7 | BIC_GFX_rc6 | BIC_Pkgpc2 | BIC_Pkgpc3 | BIC_Pkgpc6 | BIC_Pkgpc7 | BIC_Pkgpc8 | BIC_Pkgpc9 | BIC_Pkgpc10 | BIC_CPU_LPI | BIC_SYS_LPI | BIC_Mod_c6 | BIC_Totl_c0 | BIC_Any_c0 | BIC_GFX_c0 | BIC_CPUGFX) +#define BIC_OTHER ( BIC_IRQ | BIC_SMI | BIC_ThreadC | BIC_CoreTmp | BIC_IPC) + +#define BIC_DISABLED_BY_DEFAULT (BIC_USEC | BIC_TOD | BIC_APIC | BIC_X2APIC) + +unsigned long long bic_enabled = (0xFFFFFFFFFFFFFFFFULL & ~BIC_DISABLED_BY_DEFAULT); +unsigned long long bic_present = BIC_USEC | BIC_TOD | BIC_sysfs | BIC_APIC | BIC_X2APIC; + +#define DO_BIC(COUNTER_NAME) (bic_enabled & bic_present & COUNTER_NAME) +#define DO_BIC_READ(COUNTER_NAME) (bic_present & COUNTER_NAME) +#define ENABLE_BIC(COUNTER_NAME) (bic_enabled |= COUNTER_NAME) +#define BIC_PRESENT(COUNTER_BIT) (bic_present |= COUNTER_BIT) +#define BIC_NOT_PRESENT(COUNTER_BIT) (bic_present &= ~COUNTER_BIT) +#define BIC_IS_ENABLED(COUNTER_BIT) (bic_enabled & COUNTER_BIT) + char *proc_stat = "/proc/stat"; FILE *outf; int *fd_percpu; @@ -48,6 +213,7 @@ struct timespec interval_ts = { 5, 0 }; unsigned int model_orig; unsigned int num_iterations; +unsigned int header_iterations; unsigned int debug; unsigned int quiet; unsigned int shown; @@ -159,13 +325,6 @@ int ignore_stdin; #define MAX(a, b) ((a) > (b) ? (a) : (b)) -/* - * buffer size used by sscanf() for added column names - * Usually truncated to 7 characters, but also handles 18 columns for raw 64-bit counters - */ -#define NAME_BYTES 20 -#define PATH_BYTES 128 - int backwards_count; char *progname; @@ -205,6 +364,7 @@ struct core_data { unsigned int core_temp_c; unsigned int core_energy; /* MSR_CORE_ENERGY_STAT */ unsigned int core_id; + unsigned long long core_throt_cnt; unsigned long long counter[MAX_ADDED_COUNTERS]; } *core_even, *core_odd; @@ -255,24 +415,6 @@ struct pkg_data { #define GET_PKG(pkg_base, pkg_no) (pkg_base + pkg_no) -enum counter_scope { SCOPE_CPU, SCOPE_CORE, SCOPE_PACKAGE }; -enum counter_type { COUNTER_ITEMS, COUNTER_CYCLES, COUNTER_SECONDS, COUNTER_USEC }; -enum counter_format { FORMAT_RAW, FORMAT_DELTA, FORMAT_PERCENT }; - -struct msr_counter { - unsigned int msr_num; - char name[NAME_BYTES]; - char path[PATH_BYTES]; - unsigned int width; - enum counter_type type; - enum counter_format format; - struct msr_counter *next; - unsigned int flags; -#define FLAGS_HIDE (1 << 0) -#define FLAGS_SHOW (1 << 1) -#define SYSFS_PERCPU (1 << 1) -}; - /* * The accumulated sum of MSR is defined as a monotonic * increasing MSR, it will be accumulated periodically, @@ -522,8 +664,10 @@ static int perf_instr_count_open(int cpu_num) /* counter for cpu_num, including user + kernel and all processes */ fd = perf_event_open(&pea, -1, cpu_num, -1, 0); - if (fd == -1) - err(-1, "cpu%d: perf instruction counter\n", cpu_num); + if (fd == -1) { + warn("cpu%d: perf instruction counter", cpu_num); + BIC_NOT_PRESENT(BIC_IPC); + } return fd; } @@ -550,143 +694,10 @@ int get_msr(int cpu, off_t offset, unsigned long long *msr) return 0; } -/* - * This list matches the column headers, except - * 1. built-in only, the sysfs counters are not here -- we learn of those at run-time - * 2. Core and CPU are moved to the end, we can't have strings that contain them - * matching on them for --show and --hide. - */ -struct msr_counter bic[] = { - { 0x0, "usec" }, - { 0x0, "Time_Of_Day_Seconds" }, - { 0x0, "Package" }, - { 0x0, "Node" }, - { 0x0, "Avg_MHz" }, - { 0x0, "Busy%" }, - { 0x0, "Bzy_MHz" }, - { 0x0, "TSC_MHz" }, - { 0x0, "IRQ" }, - { 0x0, "SMI", "", 32, 0, FORMAT_DELTA, NULL }, - { 0x0, "sysfs" }, - { 0x0, "CPU%c1" }, - { 0x0, "CPU%c3" }, - { 0x0, "CPU%c6" }, - { 0x0, "CPU%c7" }, - { 0x0, "ThreadC" }, - { 0x0, "CoreTmp" }, - { 0x0, "CoreCnt" }, - { 0x0, "PkgTmp" }, - { 0x0, "GFX%rc6" }, - { 0x0, "GFXMHz" }, - { 0x0, "Pkg%pc2" }, - { 0x0, "Pkg%pc3" }, - { 0x0, "Pkg%pc6" }, - { 0x0, "Pkg%pc7" }, - { 0x0, "Pkg%pc8" }, - { 0x0, "Pkg%pc9" }, - { 0x0, "Pk%pc10" }, - { 0x0, "CPU%LPI" }, - { 0x0, "SYS%LPI" }, - { 0x0, "PkgWatt" }, - { 0x0, "CorWatt" }, - { 0x0, "GFXWatt" }, - { 0x0, "PkgCnt" }, - { 0x0, "RAMWatt" }, - { 0x0, "PKG_%" }, - { 0x0, "RAM_%" }, - { 0x0, "Pkg_J" }, - { 0x0, "Cor_J" }, - { 0x0, "GFX_J" }, - { 0x0, "RAM_J" }, - { 0x0, "Mod%c6" }, - { 0x0, "Totl%C0" }, - { 0x0, "Any%C0" }, - { 0x0, "GFX%C0" }, - { 0x0, "CPUGFX%" }, - { 0x0, "Core" }, - { 0x0, "CPU" }, - { 0x0, "APIC" }, - { 0x0, "X2APIC" }, - { 0x0, "Die" }, - { 0x0, "GFXAMHz" }, - { 0x0, "IPC" }, -}; - -#define MAX_BIC (sizeof(bic) / sizeof(struct msr_counter)) -#define BIC_USEC (1ULL << 0) -#define BIC_TOD (1ULL << 1) -#define BIC_Package (1ULL << 2) -#define BIC_Node (1ULL << 3) -#define BIC_Avg_MHz (1ULL << 4) -#define BIC_Busy (1ULL << 5) -#define BIC_Bzy_MHz (1ULL << 6) -#define BIC_TSC_MHz (1ULL << 7) -#define BIC_IRQ (1ULL << 8) -#define BIC_SMI (1ULL << 9) -#define BIC_sysfs (1ULL << 10) -#define BIC_CPU_c1 (1ULL << 11) -#define BIC_CPU_c3 (1ULL << 12) -#define BIC_CPU_c6 (1ULL << 13) -#define BIC_CPU_c7 (1ULL << 14) -#define BIC_ThreadC (1ULL << 15) -#define BIC_CoreTmp (1ULL << 16) -#define BIC_CoreCnt (1ULL << 17) -#define BIC_PkgTmp (1ULL << 18) -#define BIC_GFX_rc6 (1ULL << 19) -#define BIC_GFXMHz (1ULL << 20) -#define BIC_Pkgpc2 (1ULL << 21) -#define BIC_Pkgpc3 (1ULL << 22) -#define BIC_Pkgpc6 (1ULL << 23) -#define BIC_Pkgpc7 (1ULL << 24) -#define BIC_Pkgpc8 (1ULL << 25) -#define BIC_Pkgpc9 (1ULL << 26) -#define BIC_Pkgpc10 (1ULL << 27) -#define BIC_CPU_LPI (1ULL << 28) -#define BIC_SYS_LPI (1ULL << 29) -#define BIC_PkgWatt (1ULL << 30) -#define BIC_CorWatt (1ULL << 31) -#define BIC_GFXWatt (1ULL << 32) -#define BIC_PkgCnt (1ULL << 33) -#define BIC_RAMWatt (1ULL << 34) -#define BIC_PKG__ (1ULL << 35) -#define BIC_RAM__ (1ULL << 36) -#define BIC_Pkg_J (1ULL << 37) -#define BIC_Cor_J (1ULL << 38) -#define BIC_GFX_J (1ULL << 39) -#define BIC_RAM_J (1ULL << 40) -#define BIC_Mod_c6 (1ULL << 41) -#define BIC_Totl_c0 (1ULL << 42) -#define BIC_Any_c0 (1ULL << 43) -#define BIC_GFX_c0 (1ULL << 44) -#define BIC_CPUGFX (1ULL << 45) -#define BIC_Core (1ULL << 46) -#define BIC_CPU (1ULL << 47) -#define BIC_APIC (1ULL << 48) -#define BIC_X2APIC (1ULL << 49) -#define BIC_Die (1ULL << 50) -#define BIC_GFXACTMHz (1ULL << 51) -#define BIC_IPC (1ULL << 52) - -#define BIC_TOPOLOGY (BIC_Package | BIC_Node | BIC_CoreCnt | BIC_PkgCnt | BIC_Core | BIC_CPU | BIC_Die ) -#define BIC_THERMAL_PWR ( BIC_CoreTmp | BIC_PkgTmp | BIC_PkgWatt | BIC_CorWatt | BIC_GFXWatt | BIC_RAMWatt | BIC_PKG__ | BIC_RAM__) -#define BIC_FREQUENCY ( BIC_Avg_MHz | BIC_Busy | BIC_Bzy_MHz | BIC_TSC_MHz | BIC_GFXMHz | BIC_GFXACTMHz ) -#define BIC_IDLE ( BIC_sysfs | BIC_CPU_c1 | BIC_CPU_c3 | BIC_CPU_c6 | BIC_CPU_c7 | BIC_GFX_rc6 | BIC_Pkgpc2 | BIC_Pkgpc3 | BIC_Pkgpc6 | BIC_Pkgpc7 | BIC_Pkgpc8 | BIC_Pkgpc9 | BIC_Pkgpc10 | BIC_CPU_LPI | BIC_SYS_LPI | BIC_Mod_c6 | BIC_Totl_c0 | BIC_Any_c0 | BIC_GFX_c0 | BIC_CPUGFX) -#define BIC_OTHER ( BIC_IRQ | BIC_SMI | BIC_ThreadC | BIC_CoreTmp | BIC_IPC) - -#define BIC_DISABLED_BY_DEFAULT (BIC_USEC | BIC_TOD | BIC_APIC | BIC_X2APIC) - -unsigned long long bic_enabled = (0xFFFFFFFFFFFFFFFFULL & ~BIC_DISABLED_BY_DEFAULT); -unsigned long long bic_present = BIC_USEC | BIC_TOD | BIC_sysfs | BIC_APIC | BIC_X2APIC; - -#define DO_BIC(COUNTER_NAME) (bic_enabled & bic_present & COUNTER_NAME) -#define DO_BIC_READ(COUNTER_NAME) (bic_present & COUNTER_NAME) -#define ENABLE_BIC(COUNTER_NAME) (bic_enabled |= COUNTER_NAME) -#define BIC_PRESENT(COUNTER_BIT) (bic_present |= COUNTER_BIT) -#define BIC_NOT_PRESENT(COUNTER_BIT) (bic_present &= ~COUNTER_BIT) -#define BIC_IS_ENABLED(COUNTER_BIT) (bic_enabled & COUNTER_BIT) - #define MAX_DEFERRED 16 +char *deferred_add_names[MAX_DEFERRED]; char *deferred_skip_names[MAX_DEFERRED]; +int deferred_add_index; int deferred_skip_index; /* @@ -720,6 +731,8 @@ void help(void) " -l, --list list column headers only\n" " -n, --num_iterations num\n" " number of the measurement iterations\n" + " -N, --header_iterations num\n" + " print header every num iterations\n" " -o, --out file\n" " create or truncate \"file\" for all output\n" " -q, --quiet skip decoding system configuration header\n" @@ -741,7 +754,7 @@ void help(void) */ unsigned long long bic_lookup(char *name_list, enum show_hide_mode mode) { - int i; + unsigned int i; unsigned long long retval = 0; while (name_list) { @@ -752,40 +765,51 @@ unsigned long long bic_lookup(char *name_list, enum show_hide_mode mode) if (comma) *comma = '\0'; - if (!strcmp(name_list, "all")) - return ~0; - if (!strcmp(name_list, "topology")) - return BIC_TOPOLOGY; - if (!strcmp(name_list, "power")) - return BIC_THERMAL_PWR; - if (!strcmp(name_list, "idle")) - return BIC_IDLE; - if (!strcmp(name_list, "frequency")) - return BIC_FREQUENCY; - if (!strcmp(name_list, "other")) - return BIC_OTHER; - if (!strcmp(name_list, "all")) - return 0; - for (i = 0; i < MAX_BIC; ++i) { if (!strcmp(name_list, bic[i].name)) { retval |= (1ULL << i); break; } + if (!strcmp(name_list, "all")) { + retval |= ~0; + break; + } else if (!strcmp(name_list, "topology")) { + retval |= BIC_TOPOLOGY; + break; + } else if (!strcmp(name_list, "power")) { + retval |= BIC_THERMAL_PWR; + break; + } else if (!strcmp(name_list, "idle")) { + retval |= BIC_IDLE; + break; + } else if (!strcmp(name_list, "frequency")) { + retval |= BIC_FREQUENCY; + break; + } else if (!strcmp(name_list, "other")) { + retval |= BIC_OTHER; + break; + } + } if (i == MAX_BIC) { if (mode == SHOW_LIST) { - fprintf(stderr, "Invalid counter name: %s\n", name_list); - exit(-1); - } - deferred_skip_names[deferred_skip_index++] = name_list; - if (debug) - fprintf(stderr, "deferred \"%s\"\n", name_list); - if (deferred_skip_index >= MAX_DEFERRED) { - fprintf(stderr, "More than max %d un-recognized --skip options '%s'\n", - MAX_DEFERRED, name_list); - help(); - exit(1); + deferred_add_names[deferred_add_index++] = name_list; + if (deferred_add_index >= MAX_DEFERRED) { + fprintf(stderr, "More than max %d un-recognized --add options '%s'\n", + MAX_DEFERRED, name_list); + help(); + exit(1); + } + } else { + deferred_skip_names[deferred_skip_index++] = name_list; + if (debug) + fprintf(stderr, "deferred \"%s\"\n", name_list); + if (deferred_skip_index >= MAX_DEFERRED) { + fprintf(stderr, "More than max %d un-recognized --skip options '%s'\n", + MAX_DEFERRED, name_list); + help(); + exit(1); + } } } @@ -872,6 +896,9 @@ void print_header(char *delim) if (DO_BIC(BIC_CoreTmp)) outp += sprintf(outp, "%sCoreTmp", (printed++ ? delim : "")); + if (DO_BIC(BIC_CORE_THROT_CNT)) + outp += sprintf(outp, "%sCoreThr", (printed++ ? delim : "")); + if (do_rapl && !rapl_joules) { if (DO_BIC(BIC_CorWatt) && (do_rapl & RAPL_PER_CORE_ENERGY)) outp += sprintf(outp, "%sCorWatt", (printed++ ? delim : "")); @@ -1011,6 +1038,7 @@ int dump_counters(struct thread_data *t, struct core_data *c, struct pkg_data *p outp += sprintf(outp, "c6: %016llX\n", c->c6); outp += sprintf(outp, "c7: %016llX\n", c->c7); outp += sprintf(outp, "DTS: %dC\n", c->core_temp_c); + outp += sprintf(outp, "cpu_throt_count: %016llX\n", c->core_throt_cnt); outp += sprintf(outp, "Joules: %0X\n", c->core_energy); for (i = 0, mp = sys.cp; mp; i++, mp = mp->next) { @@ -1225,6 +1253,10 @@ int format_counters(struct thread_data *t, struct core_data *c, struct pkg_data if (DO_BIC(BIC_CoreTmp)) outp += sprintf(outp, "%s%d", (printed++ ? delim : ""), c->core_temp_c); + /* Core throttle count */ + if (DO_BIC(BIC_CORE_THROT_CNT)) + outp += sprintf(outp, "%s%lld", (printed++ ? delim : ""), c->core_throt_cnt); + for (i = 0, mp = sys.cp; mp; i++, mp = mp->next) { if (mp->format == FORMAT_RAW) { if (mp->width == 32) @@ -1311,6 +1343,7 @@ int format_counters(struct thread_data *t, struct core_data *c, struct pkg_data if (DO_BIC(BIC_PkgWatt)) outp += sprintf(outp, fmt8, (printed++ ? delim : ""), p->energy_pkg * rapl_energy_units / interval_float); + if (DO_BIC(BIC_CorWatt) && !(do_rapl & RAPL_PER_CORE_ENERGY)) outp += sprintf(outp, fmt8, (printed++ ? delim : ""), p->energy_cores * rapl_energy_units / interval_float); @@ -1386,14 +1419,14 @@ void flush_output_stderr(void) void format_all_counters(struct thread_data *t, struct core_data *c, struct pkg_data *p) { - static int printed; + static int count; - if (!printed || !summary_only) + if ((!count || (header_iterations && !(count % header_iterations))) || !summary_only) print_header("\t"); format_counters(&average.threads, &average.cores, &average.packages); - printed = 1; + count++; if (summary_only) return; @@ -1467,6 +1500,7 @@ void delta_core(struct core_data *new, struct core_data *old) old->c6 = new->c6 - old->c6; old->c7 = new->c7 - old->c7; old->core_temp_c = new->core_temp_c; + old->core_throt_cnt = new->core_throt_cnt; old->mc6_us = new->mc6_us - old->mc6_us; DELTA_WRAP32(new->core_energy, old->core_energy); @@ -1626,6 +1660,7 @@ void clear_counters(struct thread_data *t, struct core_data *c, struct pkg_data c->mc6_us = 0; c->core_temp_c = 0; c->core_energy = 0; + c->core_throt_cnt = 0; p->pkg_wtd_core_c0 = 0; p->pkg_any_core_c0 = 0; @@ -1710,6 +1745,7 @@ int sum_counters(struct thread_data *t, struct core_data *c, struct pkg_data *p) average.cores.mc6_us += c->mc6_us; average.cores.core_temp_c = MAX(average.cores.core_temp_c, c->core_temp_c); + average.cores.core_throt_cnt = MAX(average.cores.core_throt_cnt, c->core_throt_cnt); average.cores.core_energy += c->core_energy; @@ -1987,6 +2023,26 @@ void get_apic_id(struct thread_data *t) fprintf(outf, "cpu%d: BIOS BUG: apic 0x%x x2apic 0x%x\n", t->cpu_id, t->apic_id, t->x2apic_id); } +int get_core_throt_cnt(int cpu, unsigned long long *cnt) +{ + char path[128 + PATH_BYTES]; + unsigned long long tmp; + FILE *fp; + int ret; + + sprintf(path, "/sys/devices/system/cpu/cpu%d/thermal_throttle/core_throttle_count", cpu); + fp = fopen(path, "r"); + if (!fp) + return -1; + ret = fscanf(fp, "%lld", &tmp); + if (ret != 1) + return -1; + fclose(fp); + *cnt = tmp; + + return 0; +} + /* * get_counters(...) * migrate to cpu @@ -2129,6 +2185,9 @@ retry: c->core_temp_c = tj_max - ((msr >> 16) & 0x7F); } + if (DO_BIC(BIC_CORE_THROT_CNT)) + get_core_throt_cnt(cpu, &c->core_throt_cnt); + if (do_rapl & RAPL_AMD_F17H) { if (get_msr(cpu, MSR_CORE_ENERGY_STAT, &msr)) return -14; @@ -2428,6 +2487,9 @@ int has_turbo_ratio_group_limits(int family, int model) if (!genuine_intel) return 0; + if (family != 6) + return 0; + switch (model) { case INTEL_FAM6_ATOM_GOLDMONT: case INTEL_FAM6_SKYLAKE_X: @@ -2435,8 +2497,9 @@ int has_turbo_ratio_group_limits(int family, int model) case INTEL_FAM6_ATOM_GOLDMONT_D: case INTEL_FAM6_ATOM_TREMONT_D: return 1; + default: + return 0; } - return 0; } static void dump_turbo_ratio_limits(int family, int model) @@ -3027,6 +3090,8 @@ void set_max_cpu_num(void) */ int count_cpus(int cpu) { + UNUSED(cpu); + topo.num_cpus++; return 0; } @@ -3361,6 +3426,9 @@ static int update_msr_sum(struct thread_data *t, struct core_data *c, struct pkg int i, ret; int cpu = t->cpu_id; + UNUSED(c); + UNUSED(p); + for (i = IDX_PKG_ENERGY; i < IDX_COUNT; i++) { unsigned long long msr_cur, msr_last; off_t offset; @@ -3387,6 +3455,8 @@ static int update_msr_sum(struct thread_data *t, struct core_data *c, struct pkg static void msr_record_handler(union sigval v) { + UNUSED(v); + for_all_cpus(update_msr_sum, EVEN_COUNTERS); } @@ -3439,6 +3509,9 @@ release_msr: /* * set_my_sched_priority(pri) * return previous + * + * if non-root, do this: + * # /sbin/setcap cap_sys_rawio,cap_sys_nice=+ep /usr/bin/turbostat */ int set_my_sched_priority(int priority) { @@ -3457,7 +3530,7 @@ int set_my_sched_priority(int priority) errno = 0; retval = getpriority(PRIO_PROCESS, 0); if (retval != priority) - err(-1, "getpriority(%d) != setpriority(%d)", retval, priority); + err(retval, "getpriority(%d) != setpriority(%d)", retval, priority); return original_priority; } @@ -3466,7 +3539,7 @@ void turbostat_loop() { int retval; int restarted = 0; - int done_iters = 0; + unsigned int done_iters = 0; setup_signal_handler(); @@ -3678,6 +3751,7 @@ int probe_nhm_msrs(unsigned int family, unsigned int model) break; case INTEL_FAM6_ATOM_SILVERMONT: /* BYT */ no_MSR_MISC_PWR_MGMT = 1; + /* FALLTHRU */ case INTEL_FAM6_ATOM_SILVERMONT_D: /* AVN */ pkg_cstate_limits = slv_pkg_cstate_limits; break; @@ -3721,6 +3795,9 @@ int has_slv_msrs(unsigned int family, unsigned int model) if (!genuine_intel) return 0; + if (family != 6) + return 0; + switch (model) { case INTEL_FAM6_ATOM_SILVERMONT: case INTEL_FAM6_ATOM_SILVERMONT_MID: @@ -3736,6 +3813,9 @@ int is_dnv(unsigned int family, unsigned int model) if (!genuine_intel) return 0; + if (family != 6) + return 0; + switch (model) { case INTEL_FAM6_ATOM_GOLDMONT_D: return 1; @@ -3749,6 +3829,9 @@ int is_bdx(unsigned int family, unsigned int model) if (!genuine_intel) return 0; + if (family != 6) + return 0; + switch (model) { case INTEL_FAM6_BROADWELL_X: return 1; @@ -3762,6 +3845,9 @@ int is_skx(unsigned int family, unsigned int model) if (!genuine_intel) return 0; + if (family != 6) + return 0; + switch (model) { case INTEL_FAM6_SKYLAKE_X: return 1; @@ -3775,6 +3861,9 @@ int is_icx(unsigned int family, unsigned int model) if (!genuine_intel) return 0; + if (family != 6) + return 0; + switch (model) { case INTEL_FAM6_ICELAKE_X: return 1; @@ -3787,6 +3876,9 @@ int is_ehl(unsigned int family, unsigned int model) if (!genuine_intel) return 0; + if (family != 6) + return 0; + switch (model) { case INTEL_FAM6_ATOM_TREMONT: return 1; @@ -3799,6 +3891,9 @@ int is_jvl(unsigned int family, unsigned int model) if (!genuine_intel) return 0; + if (family != 6) + return 0; + switch (model) { case INTEL_FAM6_ATOM_TREMONT_D: return 1; @@ -3811,6 +3906,9 @@ int has_turbo_ratio_limit(unsigned int family, unsigned int model) if (has_slv_msrs(family, model)) return 0; + if (family != 6) + return 0; + switch (model) { /* Nehalem compatible, but do not include turbo-ratio limit support */ case INTEL_FAM6_NEHALEM_EX: /* Nehalem-EX Xeon - Beckton */ @@ -4125,6 +4223,9 @@ int print_epb(struct thread_data *t, struct core_data *c, struct pkg_data *p) char *epb_string; int cpu, epb; + UNUSED(c); + UNUSED(p); + if (!has_epb) return 0; @@ -4171,6 +4272,9 @@ int print_hwp(struct thread_data *t, struct core_data *c, struct pkg_data *p) unsigned long long msr; int cpu; + UNUSED(c); + UNUSED(p); + if (!has_hwp) return 0; @@ -4254,6 +4358,9 @@ int print_perf_limit(struct thread_data *t, struct core_data *c, struct pkg_data unsigned long long msr; int cpu; + UNUSED(c); + UNUSED(p); + cpu = t->cpu_id; /* per-package */ @@ -4359,6 +4466,8 @@ double get_tdp_intel(unsigned int model) double get_tdp_amd(unsigned int family) { + UNUSED(family); + /* This is the max stock TDP of HEDT/Server Fam17h+ chips */ return 280.0; } @@ -4376,6 +4485,7 @@ static double rapl_dram_energy_units_probe(int model, double rapl_energy_units) case INTEL_FAM6_BROADWELL_X: /* BDX */ case INTEL_FAM6_SKYLAKE_X: /* SKX */ case INTEL_FAM6_XEON_PHI_KNL: /* KNL */ + case INTEL_FAM6_ICELAKE_X: /* ICX */ return (rapl_dram_energy_units = 15.3 / 1000000); default: return (rapl_energy_units); @@ -4559,6 +4669,8 @@ void rapl_probe_amd(unsigned int family, unsigned int model) unsigned int has_rapl = 0; double tdp; + UNUSED(model); + if (max_extended_level >= 0x80000007) { __cpuid(0x80000007, eax, ebx, ecx, edx); /* RAPL (Fam 17h+) */ @@ -4617,6 +4729,7 @@ void perf_limit_reasons_probe(unsigned int family, unsigned int model) case INTEL_FAM6_HASWELL_L: /* HSW */ case INTEL_FAM6_HASWELL_G: /* HSW */ do_gfx_perf_limit_reasons = 1; + /* FALLTHRU */ case INTEL_FAM6_HASWELL_X: /* HSX */ do_core_perf_limit_reasons = 1; do_ring_perf_limit_reasons = 1; @@ -4643,6 +4756,9 @@ int print_thermal(struct thread_data *t, struct core_data *c, struct pkg_data *p unsigned int dts, dts2; int cpu; + UNUSED(c); + UNUSED(p); + if (!(do_dts || do_ptm)) return 0; @@ -4698,7 +4814,7 @@ int print_thermal(struct thread_data *t, struct core_data *c, struct pkg_data *p void print_power_limit_msr(int cpu, unsigned long long msr, char *label) { - fprintf(outf, "cpu%d: %s: %sabled (%f Watts, %f sec, clamp %sabled)\n", + fprintf(outf, "cpu%d: %s: %sabled (%0.3f Watts, %f sec, clamp %sabled)\n", cpu, label, ((msr >> 15) & 1) ? "EN" : "DIS", ((msr >> 0) & 0x7FFF) * rapl_power_units, @@ -4714,6 +4830,9 @@ int print_rapl(struct thread_data *t, struct core_data *c, struct pkg_data *p) const char *msr_name; int cpu; + UNUSED(c); + UNUSED(p); + if (!do_rapl) return 0; @@ -4762,12 +4881,19 @@ int print_rapl(struct thread_data *t, struct core_data *c, struct pkg_data *p) cpu, msr, (msr >> 63) & 1 ? "" : "UN"); print_power_limit_msr(cpu, msr, "PKG Limit #1"); - fprintf(outf, "cpu%d: PKG Limit #2: %sabled (%f Watts, %f* sec, clamp %sabled)\n", + fprintf(outf, "cpu%d: PKG Limit #2: %sabled (%0.3f Watts, %f* sec, clamp %sabled)\n", cpu, ((msr >> 47) & 1) ? "EN" : "DIS", ((msr >> 32) & 0x7FFF) * rapl_power_units, (1.0 + (((msr >> 54) & 0x3) / 4.0)) * (1 << ((msr >> 49) & 0x1F)) * rapl_time_units, ((msr >> 48) & 1) ? "EN" : "DIS"); + + if (get_msr(cpu, MSR_VR_CURRENT_CONFIG, &msr)) + return -9; + + fprintf(outf, "cpu%d: MSR_VR_CURRENT_CONFIG: 0x%08llx\n", cpu, msr); + fprintf(outf, "cpu%d: PKG Limit #4: %f Watts (%slocked)\n", + cpu, ((msr >> 0) & 0x1FFF) * rapl_power_units, (msr >> 31) & 1 ? "" : "UN"); } if (do_rapl & RAPL_DRAM_POWER_INFO) { @@ -4830,6 +4956,9 @@ int has_snb_msrs(unsigned int family, unsigned int model) if (!genuine_intel) return 0; + if (family != 6) + return 0; + switch (model) { case INTEL_FAM6_SANDYBRIDGE: case INTEL_FAM6_SANDYBRIDGE_X: @@ -4873,6 +5002,9 @@ int has_c8910_msrs(unsigned int family, unsigned int model) if (!genuine_intel) return 0; + if (family != 6) + return 0; + switch (model) { case INTEL_FAM6_HASWELL_L: /* HSW */ case INTEL_FAM6_BROADWELL: /* BDW */ @@ -4899,6 +5031,9 @@ int has_skl_msrs(unsigned int family, unsigned int model) if (!genuine_intel) return 0; + if (family != 6) + return 0; + switch (model) { case INTEL_FAM6_SKYLAKE_L: /* SKL */ case INTEL_FAM6_CANNONLAKE_L: /* CNL */ @@ -4911,6 +5046,10 @@ int is_slm(unsigned int family, unsigned int model) { if (!genuine_intel) return 0; + + if (family != 6) + return 0; + switch (model) { case INTEL_FAM6_ATOM_SILVERMONT: /* BYT */ case INTEL_FAM6_ATOM_SILVERMONT_D: /* AVN */ @@ -4923,6 +5062,10 @@ int is_knl(unsigned int family, unsigned int model) { if (!genuine_intel) return 0; + + if (family != 6) + return 0; + switch (model) { case INTEL_FAM6_XEON_PHI_KNL: /* KNL */ return 1; @@ -4935,6 +5078,9 @@ int is_cnl(unsigned int family, unsigned int model) if (!genuine_intel) return 0; + if (family != 6) + return 0; + switch (model) { case INTEL_FAM6_CANNONLAKE_L: /* CNL */ return 1; @@ -4989,6 +5135,9 @@ int get_cpu_type(struct thread_data *t, struct core_data *c, struct pkg_data *p) { unsigned int eax, ebx, ecx, edx; + UNUSED(c); + UNUSED(p); + if (!genuine_intel) return 0; @@ -5025,6 +5174,9 @@ int set_temperature_target(struct thread_data *t, struct core_data *c, struct pk unsigned int tcc_default, tcc_offset; int cpu; + UNUSED(c); + UNUSED(p); + /* tj_max is used only for dts or ptm */ if (!(do_dts || do_ptm)) return 0; @@ -5572,6 +5724,11 @@ void process_cpuid() else BIC_NOT_PRESENT(BIC_CPU_LPI); + if (!access("/sys/devices/system/cpu/cpu0/thermal_throttle/core_throttle_count", R_OK)) + BIC_PRESENT(BIC_CORE_THROT_CNT); + else + BIC_NOT_PRESENT(BIC_CORE_THROT_CNT); + if (!access(sys_lpi_file_sysfs, R_OK)) { sys_lpi_file = sys_lpi_file_sysfs; BIC_PRESENT(BIC_SYS_LPI); @@ -5601,11 +5758,6 @@ int dir_filter(const struct dirent *dirp) return 0; } -int open_dev_cpu_msr(int dummy1) -{ - return 0; -} - void topology_probe() { int i; @@ -5896,6 +6048,9 @@ void turbostat_init() if (!quiet && do_irtl_snb) print_irtl(); + + if (DO_BIC(BIC_IPC)) + (void)get_instr_count_fd(base_cpu); } int fork_it(char **argv) @@ -5973,7 +6128,7 @@ int get_and_dump_counters(void) void print_version() { - fprintf(outf, "turbostat version 21.05.04" " - Len Brown \n"); + fprintf(outf, "turbostat version 2022.04.16 - Len Brown \n"); } int add_counter(unsigned int msr_num, char *path, char *name, @@ -6138,6 +6293,16 @@ next: } } +int is_deferred_add(char *name) +{ + int i; + + for (i = 0; i < deferred_add_index; ++i) + if (!strcmp(name, deferred_add_names[i])) + return 1; + return 0; +} + int is_deferred_skip(char *name) { int i; @@ -6156,9 +6321,6 @@ void probe_sysfs(void) int state; char *sp; - if (!DO_BIC(BIC_sysfs)) - return; - for (state = 10; state >= 0; --state) { sprintf(path, "/sys/devices/system/cpu/cpu%d/cpuidle/state%d/name", base_cpu, state); @@ -6181,6 +6343,9 @@ void probe_sysfs(void) sprintf(path, "cpuidle/state%d/time", state); + if (!DO_BIC(BIC_sysfs) && !is_deferred_add(name_buf)) + continue; + if (is_deferred_skip(name_buf)) continue; @@ -6206,6 +6371,9 @@ void probe_sysfs(void) sprintf(path, "cpuidle/state%d/usage", state); + if (!DO_BIC(BIC_sysfs) && !is_deferred_add(name_buf)) + continue; + if (is_deferred_skip(name_buf)) continue; @@ -6313,6 +6481,7 @@ void cmdline(int argc, char **argv) { "interval", required_argument, 0, 'i' }, { "IPC", no_argument, 0, 'I' }, { "num_iterations", required_argument, 0, 'n' }, + { "header_iterations", required_argument, 0, 'N' }, { "help", no_argument, 0, 'h' }, { "hide", required_argument, 0, 'H' }, // meh, -h taken by --help { "Joules", no_argument, 0, 'J' }, @@ -6394,6 +6563,14 @@ void cmdline(int argc, char **argv) exit(2); } break; + case 'N': + header_iterations = strtod(optarg, NULL); + + if (header_iterations <= 0) { + fprintf(outf, "iterations %d should be positive number\n", header_iterations); + exit(2); + } + break; case 's': /* * --show: show only those specified @@ -6432,6 +6609,8 @@ int main(int argc, char **argv) turbostat_init(); + msr_sum_record(); + /* dump counters and exit */ if (dump_only) return get_and_dump_counters(); @@ -6443,7 +6622,6 @@ int main(int argc, char **argv) return 0; } - msr_sum_record(); /* * if any params left, it must be a command to fork */ diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c index ca40abe9daa8..da9290817866 100644 --- a/tools/testing/selftests/landlock/base_test.c +++ b/tools/testing/selftests/landlock/base_test.c @@ -18,10 +18,11 @@ #include "common.h" #ifndef O_PATH -#define O_PATH 010000000 +#define O_PATH 010000000 #endif -TEST(inconsistent_attr) { +TEST(inconsistent_attr) +{ const long page_size = sysconf(_SC_PAGESIZE); char *const buf = malloc(page_size + 1); struct landlock_ruleset_attr *const ruleset_attr = (void *)buf; @@ -34,20 +35,26 @@ TEST(inconsistent_attr) { ASSERT_EQ(EINVAL, errno); ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 1, 0)); ASSERT_EQ(EINVAL, errno); + ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 7, 0)); + ASSERT_EQ(EINVAL, errno); ASSERT_EQ(-1, landlock_create_ruleset(NULL, 1, 0)); /* The size if less than sizeof(struct landlock_attr_enforce). */ ASSERT_EQ(EFAULT, errno); - ASSERT_EQ(-1, landlock_create_ruleset(NULL, - sizeof(struct landlock_ruleset_attr), 0)); + ASSERT_EQ(-1, landlock_create_ruleset( + NULL, sizeof(struct landlock_ruleset_attr), 0)); ASSERT_EQ(EFAULT, errno); ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size + 1, 0)); ASSERT_EQ(E2BIG, errno); - ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, - sizeof(struct landlock_ruleset_attr), 0)); + /* Checks minimal valid attribute size. */ + ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 8, 0)); + ASSERT_EQ(ENOMSG, errno); + ASSERT_EQ(-1, landlock_create_ruleset( + ruleset_attr, + sizeof(struct landlock_ruleset_attr), 0)); ASSERT_EQ(ENOMSG, errno); ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size, 0)); ASSERT_EQ(ENOMSG, errno); @@ -63,38 +70,44 @@ TEST(inconsistent_attr) { free(buf); } -TEST(abi_version) { +TEST(abi_version) +{ const struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, }; - ASSERT_EQ(1, landlock_create_ruleset(NULL, 0, - LANDLOCK_CREATE_RULESET_VERSION)); + ASSERT_EQ(2, landlock_create_ruleset(NULL, 0, + LANDLOCK_CREATE_RULESET_VERSION)); ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, - LANDLOCK_CREATE_RULESET_VERSION)); + LANDLOCK_CREATE_RULESET_VERSION)); ASSERT_EQ(EINVAL, errno); ASSERT_EQ(-1, landlock_create_ruleset(NULL, sizeof(ruleset_attr), - LANDLOCK_CREATE_RULESET_VERSION)); + LANDLOCK_CREATE_RULESET_VERSION)); ASSERT_EQ(EINVAL, errno); - ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, - sizeof(ruleset_attr), - LANDLOCK_CREATE_RULESET_VERSION)); + ASSERT_EQ(-1, + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), + LANDLOCK_CREATE_RULESET_VERSION)); ASSERT_EQ(EINVAL, errno); ASSERT_EQ(-1, landlock_create_ruleset(NULL, 0, - LANDLOCK_CREATE_RULESET_VERSION | 1 << 31)); + LANDLOCK_CREATE_RULESET_VERSION | + 1 << 31)); ASSERT_EQ(EINVAL, errno); } -TEST(inval_create_ruleset_flags) { +/* Tests ordering of syscall argument checks. */ +TEST(create_ruleset_checks_ordering) +{ const int last_flag = LANDLOCK_CREATE_RULESET_VERSION; const int invalid_flag = last_flag << 1; + int ruleset_fd; const struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, }; + /* Checks priority for invalid flags. */ ASSERT_EQ(-1, landlock_create_ruleset(NULL, 0, invalid_flag)); ASSERT_EQ(EINVAL, errno); @@ -102,44 +115,121 @@ TEST(inval_create_ruleset_flags) { ASSERT_EQ(EINVAL, errno); ASSERT_EQ(-1, landlock_create_ruleset(NULL, sizeof(ruleset_attr), - invalid_flag)); + invalid_flag)); ASSERT_EQ(EINVAL, errno); - ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, - sizeof(ruleset_attr), invalid_flag)); + ASSERT_EQ(-1, + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), + invalid_flag)); ASSERT_EQ(EINVAL, errno); -} -TEST(empty_path_beneath_attr) { - const struct landlock_ruleset_attr ruleset_attr = { - .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE, - }; - const int ruleset_fd = landlock_create_ruleset(&ruleset_attr, - sizeof(ruleset_attr), 0); + /* Checks too big ruleset_attr size. */ + ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, -1, 0)); + ASSERT_EQ(E2BIG, errno); + /* Checks too small ruleset_attr size. */ + ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, 0)); + ASSERT_EQ(EINVAL, errno); + ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 1, 0)); + ASSERT_EQ(EINVAL, errno); + + /* Checks valid call. */ + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); ASSERT_LE(0, ruleset_fd); - - /* Similar to struct landlock_path_beneath_attr.parent_fd = 0 */ - ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, - NULL, 0)); - ASSERT_EQ(EFAULT, errno); ASSERT_EQ(0, close(ruleset_fd)); } -TEST(inval_fd_enforce) { - ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); +/* Tests ordering of syscall argument checks. */ +TEST(add_rule_checks_ordering) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE, + }; + struct landlock_path_beneath_attr path_beneath_attr = { + .allowed_access = LANDLOCK_ACCESS_FS_EXECUTE, + .parent_fd = -1, + }; + const int ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); - ASSERT_EQ(-1, landlock_restrict_self(-1, 0)); + ASSERT_LE(0, ruleset_fd); + + /* Checks invalid flags. */ + ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 1)); + ASSERT_EQ(EINVAL, errno); + + /* Checks invalid ruleset FD. */ + ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 0)); ASSERT_EQ(EBADF, errno); + + /* Checks invalid rule type. */ + ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, 0, NULL, 0)); + ASSERT_EQ(EINVAL, errno); + + /* Checks invalid rule attr. */ + ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, + NULL, 0)); + ASSERT_EQ(EFAULT, errno); + + /* Checks invalid path_beneath.parent_fd. */ + ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, + &path_beneath_attr, 0)); + ASSERT_EQ(EBADF, errno); + + /* Checks valid call. */ + path_beneath_attr.parent_fd = + open("/tmp", O_PATH | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC); + ASSERT_LE(0, path_beneath_attr.parent_fd); + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, + &path_beneath_attr, 0)); + ASSERT_EQ(0, close(path_beneath_attr.parent_fd)); + ASSERT_EQ(0, close(ruleset_fd)); } -TEST(unpriv_enforce_without_no_new_privs) { - int err; +/* Tests ordering of syscall argument and permission checks. */ +TEST(restrict_self_checks_ordering) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE, + }; + struct landlock_path_beneath_attr path_beneath_attr = { + .allowed_access = LANDLOCK_ACCESS_FS_EXECUTE, + .parent_fd = -1, + }; + const int ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + path_beneath_attr.parent_fd = + open("/tmp", O_PATH | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC); + ASSERT_LE(0, path_beneath_attr.parent_fd); + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, + &path_beneath_attr, 0)); + ASSERT_EQ(0, close(path_beneath_attr.parent_fd)); + + /* Checks unprivileged enforcement without no_new_privs. */ drop_caps(_metadata); - err = landlock_restrict_self(-1, 0); + ASSERT_EQ(-1, landlock_restrict_self(-1, -1)); ASSERT_EQ(EPERM, errno); - ASSERT_EQ(err, -1); + ASSERT_EQ(-1, landlock_restrict_self(-1, 0)); + ASSERT_EQ(EPERM, errno); + ASSERT_EQ(-1, landlock_restrict_self(ruleset_fd, 0)); + ASSERT_EQ(EPERM, errno); + + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + + /* Checks invalid flags. */ + ASSERT_EQ(-1, landlock_restrict_self(-1, -1)); + ASSERT_EQ(EINVAL, errno); + + /* Checks invalid ruleset FD. */ + ASSERT_EQ(-1, landlock_restrict_self(-1, 0)); + ASSERT_EQ(EBADF, errno); + + /* Checks valid call. */ + ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0)); + ASSERT_EQ(0, close(ruleset_fd)); } TEST(ruleset_fd_io) @@ -151,8 +241,8 @@ TEST(ruleset_fd_io) char buf; drop_caps(_metadata); - ruleset_fd = landlock_create_ruleset(&ruleset_attr, - sizeof(ruleset_attr), 0); + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(-1, write(ruleset_fd, ".", 1)); @@ -197,14 +287,15 @@ TEST(ruleset_fd_transfer) drop_caps(_metadata); /* Creates a test ruleset with a simple rule. */ - ruleset_fd_tx = landlock_create_ruleset(&ruleset_attr, - sizeof(ruleset_attr), 0); + ruleset_fd_tx = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); ASSERT_LE(0, ruleset_fd_tx); - path_beneath_attr.parent_fd = open("/tmp", O_PATH | O_NOFOLLOW | - O_DIRECTORY | O_CLOEXEC); + path_beneath_attr.parent_fd = + open("/tmp", O_PATH | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC); ASSERT_LE(0, path_beneath_attr.parent_fd); - ASSERT_EQ(0, landlock_add_rule(ruleset_fd_tx, LANDLOCK_RULE_PATH_BENEATH, - &path_beneath_attr, 0)); + ASSERT_EQ(0, + landlock_add_rule(ruleset_fd_tx, LANDLOCK_RULE_PATH_BENEATH, + &path_beneath_attr, 0)); ASSERT_EQ(0, close(path_beneath_attr.parent_fd)); cmsg = CMSG_FIRSTHDR(&msg); @@ -215,7 +306,8 @@ TEST(ruleset_fd_transfer) memcpy(CMSG_DATA(cmsg), &ruleset_fd_tx, sizeof(ruleset_fd_tx)); /* Sends the ruleset FD over a socketpair and then close it. */ - ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, socket_fds)); + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, + socket_fds)); ASSERT_EQ(sizeof(data_tx), sendmsg(socket_fds[0], &msg, 0)); ASSERT_EQ(0, close(socket_fds[0])); ASSERT_EQ(0, close(ruleset_fd_tx)); @@ -226,7 +318,8 @@ TEST(ruleset_fd_transfer) int ruleset_fd_rx; *(char *)msg.msg_iov->iov_base = '\0'; - ASSERT_EQ(sizeof(data_tx), recvmsg(socket_fds[1], &msg, MSG_CMSG_CLOEXEC)); + ASSERT_EQ(sizeof(data_tx), + recvmsg(socket_fds[1], &msg, MSG_CMSG_CLOEXEC)); ASSERT_EQ('.', *(char *)msg.msg_iov->iov_base); ASSERT_EQ(0, close(socket_fds[1])); cmsg = CMSG_FIRSTHDR(&msg); diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h index 183b7e8e1b95..7ba18eb23783 100644 --- a/tools/testing/selftests/landlock/common.h +++ b/tools/testing/selftests/landlock/common.h @@ -25,6 +25,7 @@ * this to be possible, we must not call abort() but instead exit smoothly * (hence the step print). */ +/* clang-format off */ #define TEST_F_FORK(fixture_name, test_name) \ static void fixture_name##_##test_name##_child( \ struct __test_metadata *_metadata, \ @@ -71,11 +72,12 @@ FIXTURE_DATA(fixture_name) __attribute__((unused)) *self, \ const FIXTURE_VARIANT(fixture_name) \ __attribute__((unused)) *variant) +/* clang-format on */ #ifndef landlock_create_ruleset -static inline int landlock_create_ruleset( - const struct landlock_ruleset_attr *const attr, - const size_t size, const __u32 flags) +static inline int +landlock_create_ruleset(const struct landlock_ruleset_attr *const attr, + const size_t size, const __u32 flags) { return syscall(__NR_landlock_create_ruleset, attr, size, flags); } @@ -83,17 +85,18 @@ static inline int landlock_create_ruleset( #ifndef landlock_add_rule static inline int landlock_add_rule(const int ruleset_fd, - const enum landlock_rule_type rule_type, - const void *const rule_attr, const __u32 flags) + const enum landlock_rule_type rule_type, + const void *const rule_attr, + const __u32 flags) { - return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, - rule_attr, flags); + return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr, + flags); } #endif #ifndef landlock_restrict_self static inline int landlock_restrict_self(const int ruleset_fd, - const __u32 flags) + const __u32 flags) { return syscall(__NR_landlock_restrict_self, ruleset_fd, flags); } @@ -111,69 +114,76 @@ static void _init_caps(struct __test_metadata *const _metadata, bool drop_all) }; cap_p = cap_get_proc(); - EXPECT_NE(NULL, cap_p) { + EXPECT_NE(NULL, cap_p) + { TH_LOG("Failed to cap_get_proc: %s", strerror(errno)); } - EXPECT_NE(-1, cap_clear(cap_p)) { + EXPECT_NE(-1, cap_clear(cap_p)) + { TH_LOG("Failed to cap_clear: %s", strerror(errno)); } if (!drop_all) { EXPECT_NE(-1, cap_set_flag(cap_p, CAP_PERMITTED, - ARRAY_SIZE(caps), caps, CAP_SET)) { + ARRAY_SIZE(caps), caps, CAP_SET)) + { TH_LOG("Failed to cap_set_flag: %s", strerror(errno)); } } - EXPECT_NE(-1, cap_set_proc(cap_p)) { + EXPECT_NE(-1, cap_set_proc(cap_p)) + { TH_LOG("Failed to cap_set_proc: %s", strerror(errno)); } - EXPECT_NE(-1, cap_free(cap_p)) { + EXPECT_NE(-1, cap_free(cap_p)) + { TH_LOG("Failed to cap_free: %s", strerror(errno)); } } /* We cannot put such helpers in a library because of kselftest_harness.h . */ -__attribute__((__unused__)) -static void disable_caps(struct __test_metadata *const _metadata) +__attribute__((__unused__)) static void +disable_caps(struct __test_metadata *const _metadata) { _init_caps(_metadata, false); } -__attribute__((__unused__)) -static void drop_caps(struct __test_metadata *const _metadata) +__attribute__((__unused__)) static void +drop_caps(struct __test_metadata *const _metadata) { _init_caps(_metadata, true); } static void _effective_cap(struct __test_metadata *const _metadata, - const cap_value_t caps, const cap_flag_value_t value) + const cap_value_t caps, const cap_flag_value_t value) { cap_t cap_p; cap_p = cap_get_proc(); - EXPECT_NE(NULL, cap_p) { + EXPECT_NE(NULL, cap_p) + { TH_LOG("Failed to cap_get_proc: %s", strerror(errno)); } - EXPECT_NE(-1, cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &caps, value)) { + EXPECT_NE(-1, cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &caps, value)) + { TH_LOG("Failed to cap_set_flag: %s", strerror(errno)); } - EXPECT_NE(-1, cap_set_proc(cap_p)) { + EXPECT_NE(-1, cap_set_proc(cap_p)) + { TH_LOG("Failed to cap_set_proc: %s", strerror(errno)); } - EXPECT_NE(-1, cap_free(cap_p)) { + EXPECT_NE(-1, cap_free(cap_p)) + { TH_LOG("Failed to cap_free: %s", strerror(errno)); } } -__attribute__((__unused__)) -static void set_cap(struct __test_metadata *const _metadata, - const cap_value_t caps) +__attribute__((__unused__)) static void +set_cap(struct __test_metadata *const _metadata, const cap_value_t caps) { _effective_cap(_metadata, caps, CAP_SET); } -__attribute__((__unused__)) -static void clear_cap(struct __test_metadata *const _metadata, - const cap_value_t caps) +__attribute__((__unused__)) static void +clear_cap(struct __test_metadata *const _metadata, const cap_value_t caps) { _effective_cap(_metadata, caps, CAP_CLEAR); } diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 10c9a1e4ebd9..21a2ce8fa739 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -22,8 +22,21 @@ #include "common.h" -#define TMP_DIR "tmp" -#define BINARY_PATH "./true" +#ifndef renameat2 +int renameat2(int olddirfd, const char *oldpath, int newdirfd, + const char *newpath, unsigned int flags) +{ + return syscall(__NR_renameat2, olddirfd, oldpath, newdirfd, newpath, + flags); +} +#endif + +#ifndef RENAME_EXCHANGE +#define RENAME_EXCHANGE (1 << 1) +#endif + +#define TMP_DIR "tmp" +#define BINARY_PATH "./true" /* Paths (sibling number and depth) */ static const char dir_s1d1[] = TMP_DIR "/s1d1"; @@ -75,7 +88,7 @@ static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3"; */ static void mkdir_parents(struct __test_metadata *const _metadata, - const char *const path) + const char *const path) { char *walker; const char *parent; @@ -90,9 +103,10 @@ static void mkdir_parents(struct __test_metadata *const _metadata, continue; walker[i] = '\0'; err = mkdir(parent, 0700); - ASSERT_FALSE(err && errno != EEXIST) { - TH_LOG("Failed to create directory \"%s\": %s", - parent, strerror(errno)); + ASSERT_FALSE(err && errno != EEXIST) + { + TH_LOG("Failed to create directory \"%s\": %s", parent, + strerror(errno)); } walker[i] = '/'; } @@ -100,22 +114,24 @@ static void mkdir_parents(struct __test_metadata *const _metadata, } static void create_directory(struct __test_metadata *const _metadata, - const char *const path) + const char *const path) { mkdir_parents(_metadata, path); - ASSERT_EQ(0, mkdir(path, 0700)) { + ASSERT_EQ(0, mkdir(path, 0700)) + { TH_LOG("Failed to create directory \"%s\": %s", path, - strerror(errno)); + strerror(errno)); } } static void create_file(struct __test_metadata *const _metadata, - const char *const path) + const char *const path) { mkdir_parents(_metadata, path); - ASSERT_EQ(0, mknod(path, S_IFREG | 0700, 0)) { + ASSERT_EQ(0, mknod(path, S_IFREG | 0700, 0)) + { TH_LOG("Failed to create file \"%s\": %s", path, - strerror(errno)); + strerror(errno)); } } @@ -130,7 +146,7 @@ static int remove_path(const char *const path) goto out; } if (unlink(path) && rmdir(path)) { - if (errno != ENOENT) + if (errno != ENOENT && errno != ENOTDIR) err = errno; goto out; } @@ -221,8 +237,9 @@ static void remove_layout1(struct __test_metadata *const _metadata) EXPECT_EQ(0, remove_path(dir_s3d2)); } -FIXTURE(layout1) { -}; +/* clang-format off */ +FIXTURE(layout1) {}; +/* clang-format on */ FIXTURE_SETUP(layout1) { @@ -242,7 +259,8 @@ FIXTURE_TEARDOWN(layout1) * This helper enables to use the ASSERT_* macros and print the line number * pointing to the test caller. */ -static int test_open_rel(const int dirfd, const char *const path, const int flags) +static int test_open_rel(const int dirfd, const char *const path, + const int flags) { int fd; @@ -291,23 +309,23 @@ TEST_F_FORK(layout1, inval) { struct landlock_path_beneath_attr path_beneath = { .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, + LANDLOCK_ACCESS_FS_WRITE_FILE, .parent_fd = -1, }; struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, + LANDLOCK_ACCESS_FS_WRITE_FILE, }; int ruleset_fd; - path_beneath.parent_fd = open(dir_s1d2, O_PATH | O_DIRECTORY | - O_CLOEXEC); + path_beneath.parent_fd = + open(dir_s1d2, O_PATH | O_DIRECTORY | O_CLOEXEC); ASSERT_LE(0, path_beneath.parent_fd); ruleset_fd = open(dir_s1d1, O_PATH | O_DIRECTORY | O_CLOEXEC); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, - &path_beneath, 0)); + &path_beneath, 0)); /* Returns EBADF because ruleset_fd is not a landlock-ruleset FD. */ ASSERT_EQ(EBADF, errno); ASSERT_EQ(0, close(ruleset_fd)); @@ -315,55 +333,55 @@ TEST_F_FORK(layout1, inval) ruleset_fd = open(dir_s1d1, O_DIRECTORY | O_CLOEXEC); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, - &path_beneath, 0)); + &path_beneath, 0)); /* Returns EBADFD because ruleset_fd is not a valid ruleset. */ ASSERT_EQ(EBADFD, errno); ASSERT_EQ(0, close(ruleset_fd)); /* Gets a real ruleset. */ - ruleset_fd = landlock_create_ruleset(&ruleset_attr, - sizeof(ruleset_attr), 0); + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, - &path_beneath, 0)); + &path_beneath, 0)); ASSERT_EQ(0, close(path_beneath.parent_fd)); /* Tests without O_PATH. */ path_beneath.parent_fd = open(dir_s1d2, O_DIRECTORY | O_CLOEXEC); ASSERT_LE(0, path_beneath.parent_fd); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, - &path_beneath, 0)); + &path_beneath, 0)); ASSERT_EQ(0, close(path_beneath.parent_fd)); /* Tests with a ruleset FD. */ path_beneath.parent_fd = ruleset_fd; ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, - &path_beneath, 0)); + &path_beneath, 0)); ASSERT_EQ(EBADFD, errno); /* Checks unhandled allowed_access. */ - path_beneath.parent_fd = open(dir_s1d2, O_PATH | O_DIRECTORY | - O_CLOEXEC); + path_beneath.parent_fd = + open(dir_s1d2, O_PATH | O_DIRECTORY | O_CLOEXEC); ASSERT_LE(0, path_beneath.parent_fd); /* Test with legitimate values. */ path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_EXECUTE; ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, - &path_beneath, 0)); + &path_beneath, 0)); ASSERT_EQ(EINVAL, errno); path_beneath.allowed_access &= ~LANDLOCK_ACCESS_FS_EXECUTE; /* Test with unknown (64-bits) value. */ path_beneath.allowed_access |= (1ULL << 60); ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, - &path_beneath, 0)); + &path_beneath, 0)); ASSERT_EQ(EINVAL, errno); path_beneath.allowed_access &= ~(1ULL << 60); /* Test with no access. */ path_beneath.allowed_access = 0; ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, - &path_beneath, 0)); + &path_beneath, 0)); ASSERT_EQ(ENOMSG, errno); path_beneath.allowed_access &= ~(1ULL << 60); @@ -376,12 +394,14 @@ TEST_F_FORK(layout1, inval) ASSERT_EQ(0, close(ruleset_fd)); } +/* clang-format off */ + #define ACCESS_FILE ( \ LANDLOCK_ACCESS_FS_EXECUTE | \ LANDLOCK_ACCESS_FS_WRITE_FILE | \ LANDLOCK_ACCESS_FS_READ_FILE) -#define ACCESS_LAST LANDLOCK_ACCESS_FS_MAKE_SYM +#define ACCESS_LAST LANDLOCK_ACCESS_FS_REFER #define ACCESS_ALL ( \ ACCESS_FILE | \ @@ -394,55 +414,90 @@ TEST_F_FORK(layout1, inval) LANDLOCK_ACCESS_FS_MAKE_SOCK | \ LANDLOCK_ACCESS_FS_MAKE_FIFO | \ LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ + LANDLOCK_ACCESS_FS_MAKE_SYM | \ ACCESS_LAST) -TEST_F_FORK(layout1, file_access_rights) +/* clang-format on */ + +TEST_F_FORK(layout1, file_and_dir_access_rights) { __u64 access; int err; - struct landlock_path_beneath_attr path_beneath = {}; + struct landlock_path_beneath_attr path_beneath_file = {}, + path_beneath_dir = {}; struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = ACCESS_ALL, }; - const int ruleset_fd = landlock_create_ruleset(&ruleset_attr, - sizeof(ruleset_attr), 0); + const int ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); ASSERT_LE(0, ruleset_fd); /* Tests access rights for files. */ - path_beneath.parent_fd = open(file1_s1d2, O_PATH | O_CLOEXEC); - ASSERT_LE(0, path_beneath.parent_fd); + path_beneath_file.parent_fd = open(file1_s1d2, O_PATH | O_CLOEXEC); + ASSERT_LE(0, path_beneath_file.parent_fd); + + /* Tests access rights for directories. */ + path_beneath_dir.parent_fd = + open(dir_s1d2, O_PATH | O_DIRECTORY | O_CLOEXEC); + ASSERT_LE(0, path_beneath_dir.parent_fd); + for (access = 1; access <= ACCESS_LAST; access <<= 1) { - path_beneath.allowed_access = access; + path_beneath_dir.allowed_access = access; + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_PATH_BENEATH, + &path_beneath_dir, 0)); + + path_beneath_file.allowed_access = access; err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, - &path_beneath, 0); - if ((access | ACCESS_FILE) == ACCESS_FILE) { + &path_beneath_file, 0); + if (access & ACCESS_FILE) { ASSERT_EQ(0, err); } else { ASSERT_EQ(-1, err); ASSERT_EQ(EINVAL, errno); } } - ASSERT_EQ(0, close(path_beneath.parent_fd)); + ASSERT_EQ(0, close(path_beneath_file.parent_fd)); + ASSERT_EQ(0, close(path_beneath_dir.parent_fd)); + ASSERT_EQ(0, close(ruleset_fd)); +} + +TEST_F_FORK(layout1, unknown_access_rights) +{ + __u64 access_mask; + + for (access_mask = 1ULL << 63; access_mask != ACCESS_LAST; + access_mask >>= 1) { + struct landlock_ruleset_attr ruleset_attr = { + .handled_access_fs = access_mask, + }; + + ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0)); + ASSERT_EQ(EINVAL, errno); + } } static void add_path_beneath(struct __test_metadata *const _metadata, - const int ruleset_fd, const __u64 allowed_access, - const char *const path) + const int ruleset_fd, const __u64 allowed_access, + const char *const path) { struct landlock_path_beneath_attr path_beneath = { .allowed_access = allowed_access, }; path_beneath.parent_fd = open(path, O_PATH | O_CLOEXEC); - ASSERT_LE(0, path_beneath.parent_fd) { + ASSERT_LE(0, path_beneath.parent_fd) + { TH_LOG("Failed to open directory \"%s\": %s", path, - strerror(errno)); + strerror(errno)); } ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, - &path_beneath, 0)) { + &path_beneath, 0)) + { TH_LOG("Failed to update the ruleset with \"%s\": %s", path, - strerror(errno)); + strerror(errno)); } ASSERT_EQ(0, close(path_beneath.parent_fd)); } @@ -452,6 +507,8 @@ struct rule { __u64 access; }; +/* clang-format off */ + #define ACCESS_RO ( \ LANDLOCK_ACCESS_FS_READ_FILE | \ LANDLOCK_ACCESS_FS_READ_DIR) @@ -460,39 +517,46 @@ struct rule { ACCESS_RO | \ LANDLOCK_ACCESS_FS_WRITE_FILE) +/* clang-format on */ + static int create_ruleset(struct __test_metadata *const _metadata, - const __u64 handled_access_fs, const struct rule rules[]) + const __u64 handled_access_fs, + const struct rule rules[]) { int ruleset_fd, i; struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = handled_access_fs, }; - ASSERT_NE(NULL, rules) { + ASSERT_NE(NULL, rules) + { TH_LOG("No rule list"); } - ASSERT_NE(NULL, rules[0].path) { + ASSERT_NE(NULL, rules[0].path) + { TH_LOG("Empty rule list"); } - ruleset_fd = landlock_create_ruleset(&ruleset_attr, - sizeof(ruleset_attr), 0); - ASSERT_LE(0, ruleset_fd) { + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd) + { TH_LOG("Failed to create a ruleset: %s", strerror(errno)); } for (i = 0; rules[i].path; i++) { add_path_beneath(_metadata, ruleset_fd, rules[i].access, - rules[i].path); + rules[i].path); } return ruleset_fd; } static void enforce_ruleset(struct __test_metadata *const _metadata, - const int ruleset_fd) + const int ruleset_fd) { ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); - ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0)) { + ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0)) + { TH_LOG("Failed to enforce ruleset: %s", strerror(errno)); } } @@ -503,13 +567,14 @@ TEST_F_FORK(layout1, proc_nsfs) { .path = "/dev/null", .access = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, + LANDLOCK_ACCESS_FS_WRITE_FILE, }, - {} + {}, }; struct landlock_path_beneath_attr path_beneath; - const int ruleset_fd = create_ruleset(_metadata, rules[0].access | - LANDLOCK_ACCESS_FS_READ_DIR, rules); + const int ruleset_fd = create_ruleset( + _metadata, rules[0].access | LANDLOCK_ACCESS_FS_READ_DIR, + rules); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, test_open("/proc/self/ns/mnt", O_RDONLY)); @@ -536,22 +601,23 @@ TEST_F_FORK(layout1, proc_nsfs) * references to a ruleset. */ path_beneath.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, + LANDLOCK_ACCESS_FS_WRITE_FILE, path_beneath.parent_fd = open("/proc/self/ns/mnt", O_PATH | O_CLOEXEC); ASSERT_LE(0, path_beneath.parent_fd); ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, - &path_beneath, 0)); + &path_beneath, 0)); ASSERT_EQ(EBADFD, errno); ASSERT_EQ(0, close(path_beneath.parent_fd)); } -TEST_F_FORK(layout1, unpriv) { +TEST_F_FORK(layout1, unpriv) +{ const struct rule rules[] = { { .path = dir_s1d2, .access = ACCESS_RO, }, - {} + {}, }; int ruleset_fd; @@ -577,9 +643,9 @@ TEST_F_FORK(layout1, effective_access) { .path = file1_s2d2, .access = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, + LANDLOCK_ACCESS_FS_WRITE_FILE, }, - {} + {}, }; const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); char buf; @@ -589,17 +655,23 @@ TEST_F_FORK(layout1, effective_access) enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(0, close(ruleset_fd)); - /* Tests on a directory. */ + /* Tests on a directory (with or without O_PATH). */ ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); + ASSERT_EQ(0, test_open("/", O_RDONLY | O_PATH)); ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY)); + ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY | O_PATH)); ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); + ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY | O_PATH)); + ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY)); ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY)); ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); - /* Tests on a file. */ + /* Tests on a file (with or without O_PATH). */ ASSERT_EQ(EACCES, test_open(dir_s2d2, O_RDONLY)); + ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY | O_PATH)); + ASSERT_EQ(0, test_open(file1_s2d2, O_RDONLY)); /* Checks effective read and write actions. */ @@ -626,7 +698,7 @@ TEST_F_FORK(layout1, unhandled_access) .path = dir_s1d2, .access = ACCESS_RO, }, - {} + {}, }; /* Here, we only handle read accesses, not write accesses. */ const int ruleset_fd = create_ruleset(_metadata, ACCESS_RO, rules); @@ -653,14 +725,14 @@ TEST_F_FORK(layout1, ruleset_overlap) { .path = dir_s1d2, .access = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, + LANDLOCK_ACCESS_FS_WRITE_FILE, }, { .path = dir_s1d2, .access = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_READ_DIR, + LANDLOCK_ACCESS_FS_READ_DIR, }, - {} + {}, }; const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); @@ -687,6 +759,113 @@ TEST_F_FORK(layout1, ruleset_overlap) ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); } +TEST_F_FORK(layout1, layer_rule_unions) +{ + const struct rule layer1[] = { + { + .path = dir_s1d2, + .access = LANDLOCK_ACCESS_FS_READ_FILE, + }, + /* dir_s1d3 should allow READ_FILE and WRITE_FILE (O_RDWR). */ + { + .path = dir_s1d3, + .access = LANDLOCK_ACCESS_FS_WRITE_FILE, + }, + {}, + }; + const struct rule layer2[] = { + /* Doesn't change anything from layer1. */ + { + .path = dir_s1d2, + .access = LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_WRITE_FILE, + }, + {}, + }; + const struct rule layer3[] = { + /* Only allows write (but not read) to dir_s1d3. */ + { + .path = dir_s1d2, + .access = LANDLOCK_ACCESS_FS_WRITE_FILE, + }, + {}, + }; + int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1); + + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Checks s1d1 hierarchy with layer1. */ + ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); + ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); + ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR)); + ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); + + /* Checks s1d2 hierarchy with layer1. */ + ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); + ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); + ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR)); + ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); + + /* Checks s1d3 hierarchy with layer1. */ + ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); + ASSERT_EQ(0, test_open(file1_s1d3, O_WRONLY)); + /* dir_s1d3 should allow READ_FILE and WRITE_FILE (O_RDWR). */ + ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); + ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); + + /* Doesn't change anything from layer1. */ + ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2); + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Checks s1d1 hierarchy with layer2. */ + ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); + ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); + ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR)); + ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); + + /* Checks s1d2 hierarchy with layer2. */ + ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); + ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); + ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR)); + ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); + + /* Checks s1d3 hierarchy with layer2. */ + ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); + ASSERT_EQ(0, test_open(file1_s1d3, O_WRONLY)); + /* dir_s1d3 should allow READ_FILE and WRITE_FILE (O_RDWR). */ + ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); + ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); + + /* Only allows write (but not read) to dir_s1d3. */ + ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3); + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Checks s1d1 hierarchy with layer3. */ + ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); + ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); + ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR)); + ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); + + /* Checks s1d2 hierarchy with layer3. */ + ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDONLY)); + ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); + ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR)); + ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); + + /* Checks s1d3 hierarchy with layer3. */ + ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDONLY)); + ASSERT_EQ(0, test_open(file1_s1d3, O_WRONLY)); + /* dir_s1d3 should now deny READ_FILE and WRITE_FILE (O_RDWR). */ + ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDWR)); + ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); +} + TEST_F_FORK(layout1, non_overlapping_accesses) { const struct rule layer1[] = { @@ -694,22 +873,22 @@ TEST_F_FORK(layout1, non_overlapping_accesses) .path = dir_s1d2, .access = LANDLOCK_ACCESS_FS_MAKE_REG, }, - {} + {}, }; const struct rule layer2[] = { { .path = dir_s1d3, .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, }, - {} + {}, }; int ruleset_fd; ASSERT_EQ(0, unlink(file1_s1d1)); ASSERT_EQ(0, unlink(file1_s1d2)); - ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, - layer1); + ruleset_fd = + create_ruleset(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, layer1); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(0, close(ruleset_fd)); @@ -720,7 +899,7 @@ TEST_F_FORK(layout1, non_overlapping_accesses) ASSERT_EQ(0, unlink(file1_s1d2)); ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REMOVE_FILE, - layer2); + layer2); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(0, close(ruleset_fd)); @@ -758,7 +937,7 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) .path = file1_s1d3, .access = LANDLOCK_ACCESS_FS_READ_FILE, }, - {} + {}, }; /* First rule with write restrictions. */ const struct rule layer2_read_write[] = { @@ -766,14 +945,14 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) { .path = dir_s1d3, .access = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, + LANDLOCK_ACCESS_FS_WRITE_FILE, }, /* ...but also denies read access via its grandparent directory. */ { .path = dir_s1d2, .access = LANDLOCK_ACCESS_FS_WRITE_FILE, }, - {} + {}, }; const struct rule layer3_read[] = { /* Allows read access via its great-grandparent directory. */ @@ -781,7 +960,7 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) .path = dir_s1d1, .access = LANDLOCK_ACCESS_FS_READ_FILE, }, - {} + {}, }; const struct rule layer4_read_write[] = { /* @@ -792,7 +971,7 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) .path = dir_s1d2, .access = LANDLOCK_ACCESS_FS_READ_FILE, }, - {} + {}, }; const struct rule layer5_read[] = { /* @@ -803,7 +982,7 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) .path = dir_s1d2, .access = LANDLOCK_ACCESS_FS_READ_FILE, }, - {} + {}, }; const struct rule layer6_execute[] = { /* @@ -814,7 +993,7 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) .path = dir_s2d1, .access = LANDLOCK_ACCESS_FS_EXECUTE, }, - {} + {}, }; const struct rule layer7_read_write[] = { /* @@ -825,12 +1004,12 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) .path = dir_s1d2, .access = LANDLOCK_ACCESS_FS_WRITE_FILE, }, - {} + {}, }; int ruleset_fd; ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, - layer1_read); + layer1_read); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(0, close(ruleset_fd)); @@ -840,8 +1019,10 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY)); - ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, layer2_read_write); + ruleset_fd = create_ruleset(_metadata, + LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_WRITE_FILE, + layer2_read_write); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(0, close(ruleset_fd)); @@ -852,7 +1033,7 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY)); ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, - layer3_read); + layer3_read); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(0, close(ruleset_fd)); @@ -863,8 +1044,10 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY)); /* This time, denies write access for the file hierarchy. */ - ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, layer4_read_write); + ruleset_fd = create_ruleset(_metadata, + LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_WRITE_FILE, + layer4_read_write); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(0, close(ruleset_fd)); @@ -879,7 +1062,7 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, - layer5_read); + layer5_read); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(0, close(ruleset_fd)); @@ -891,7 +1074,7 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_EXECUTE, - layer6_execute); + layer6_execute); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(0, close(ruleset_fd)); @@ -902,8 +1085,10 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); - ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, layer7_read_write); + ruleset_fd = create_ruleset(_metadata, + LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_WRITE_FILE, + layer7_read_write); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(0, close(ruleset_fd)); @@ -921,9 +1106,9 @@ TEST_F_FORK(layout1, inherit_subset) { .path = dir_s1d2, .access = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_READ_DIR, + LANDLOCK_ACCESS_FS_READ_DIR, }, - {} + {}, }; const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); @@ -949,7 +1134,7 @@ TEST_F_FORK(layout1, inherit_subset) * ANDed with the previous ones. */ add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE, - dir_s1d2); + dir_s1d2); /* * According to ruleset_fd, dir_s1d2 should now have the * LANDLOCK_ACCESS_FS_READ_FILE and LANDLOCK_ACCESS_FS_WRITE_FILE @@ -1004,7 +1189,7 @@ TEST_F_FORK(layout1, inherit_subset) * that there was no rule tied to it before. */ add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE, - dir_s1d3); + dir_s1d3); enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(0, close(ruleset_fd)); @@ -1039,7 +1224,7 @@ TEST_F_FORK(layout1, inherit_superset) .path = dir_s1d3, .access = ACCESS_RO, }, - {} + {}, }; const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); @@ -1054,8 +1239,10 @@ TEST_F_FORK(layout1, inherit_superset) ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); /* Now dir_s1d2, parent of dir_s1d3, gets a new rule tied to it. */ - add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_READ_DIR, dir_s1d2); + add_path_beneath(_metadata, ruleset_fd, + LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_READ_DIR, + dir_s1d2); enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(0, close(ruleset_fd)); @@ -1075,12 +1262,12 @@ TEST_F_FORK(layout1, max_layers) .path = dir_s1d2, .access = ACCESS_RO, }, - {} + {}, }; const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); ASSERT_LE(0, ruleset_fd); - for (i = 0; i < 64; i++) + for (i = 0; i < 16; i++) enforce_ruleset(_metadata, ruleset_fd); for (i = 0; i < 2; i++) { @@ -1097,15 +1284,15 @@ TEST_F_FORK(layout1, empty_or_same_ruleset) int ruleset_fd; /* Tests empty handled_access_fs. */ - ruleset_fd = landlock_create_ruleset(&ruleset_attr, - sizeof(ruleset_attr), 0); + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); ASSERT_LE(-1, ruleset_fd); ASSERT_EQ(ENOMSG, errno); /* Enforces policy which deny read access to all files. */ ruleset_attr.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE; - ruleset_fd = landlock_create_ruleset(&ruleset_attr, - sizeof(ruleset_attr), 0); + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); @@ -1113,8 +1300,8 @@ TEST_F_FORK(layout1, empty_or_same_ruleset) /* Nests a policy which deny read access to all directories. */ ruleset_attr.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR; - ruleset_fd = landlock_create_ruleset(&ruleset_attr, - sizeof(ruleset_attr), 0); + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); @@ -1137,7 +1324,7 @@ TEST_F_FORK(layout1, rule_on_mountpoint) .path = dir_s3d2, .access = ACCESS_RO, }, - {} + {}, }; const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); @@ -1166,7 +1353,7 @@ TEST_F_FORK(layout1, rule_over_mountpoint) .path = dir_s3d1, .access = ACCESS_RO, }, - {} + {}, }; const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); @@ -1194,7 +1381,7 @@ TEST_F_FORK(layout1, rule_over_root_allow_then_deny) .path = "/", .access = ACCESS_RO, }, - {} + {}, }; int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); @@ -1224,7 +1411,7 @@ TEST_F_FORK(layout1, rule_over_root_deny) .path = "/", .access = LANDLOCK_ACCESS_FS_READ_FILE, }, - {} + {}, }; const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); @@ -1244,12 +1431,13 @@ TEST_F_FORK(layout1, rule_inside_mount_ns) .path = "s3d3", .access = ACCESS_RO, }, - {} + {}, }; int ruleset_fd; set_cap(_metadata, CAP_SYS_ADMIN); - ASSERT_EQ(0, syscall(SYS_pivot_root, dir_s3d2, dir_s3d3)) { + ASSERT_EQ(0, syscall(__NR_pivot_root, dir_s3d2, dir_s3d3)) + { TH_LOG("Failed to pivot root: %s", strerror(errno)); }; ASSERT_EQ(0, chdir("/")); @@ -1271,7 +1459,7 @@ TEST_F_FORK(layout1, mount_and_pivot) .path = dir_s3d2, .access = ACCESS_RO, }, - {} + {}, }; const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); @@ -1282,7 +1470,7 @@ TEST_F_FORK(layout1, mount_and_pivot) set_cap(_metadata, CAP_SYS_ADMIN); ASSERT_EQ(-1, mount(NULL, dir_s3d2, NULL, MS_RDONLY, NULL)); ASSERT_EQ(EPERM, errno); - ASSERT_EQ(-1, syscall(SYS_pivot_root, dir_s3d2, dir_s3d3)); + ASSERT_EQ(-1, syscall(__NR_pivot_root, dir_s3d2, dir_s3d3)); ASSERT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); } @@ -1294,28 +1482,29 @@ TEST_F_FORK(layout1, move_mount) .path = dir_s3d2, .access = ACCESS_RO, }, - {} + {}, }; const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); ASSERT_LE(0, ruleset_fd); set_cap(_metadata, CAP_SYS_ADMIN); - ASSERT_EQ(0, syscall(SYS_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD, - dir_s1d2, 0)) { + ASSERT_EQ(0, syscall(__NR_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD, + dir_s1d2, 0)) + { TH_LOG("Failed to move mount: %s", strerror(errno)); } - ASSERT_EQ(0, syscall(SYS_move_mount, AT_FDCWD, dir_s1d2, AT_FDCWD, - dir_s3d2, 0)); + ASSERT_EQ(0, syscall(__NR_move_mount, AT_FDCWD, dir_s1d2, AT_FDCWD, + dir_s3d2, 0)); clear_cap(_metadata, CAP_SYS_ADMIN); enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(0, close(ruleset_fd)); set_cap(_metadata, CAP_SYS_ADMIN); - ASSERT_EQ(-1, syscall(SYS_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD, - dir_s1d2, 0)); + ASSERT_EQ(-1, syscall(__NR_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD, + dir_s1d2, 0)); ASSERT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); } @@ -1335,7 +1524,7 @@ TEST_F_FORK(layout1, release_inodes) .path = dir_s3d3, .access = ACCESS_RO, }, - {} + {}, }; const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); @@ -1362,7 +1551,7 @@ enum relative_access { }; static void test_relative_path(struct __test_metadata *const _metadata, - const enum relative_access rel) + const enum relative_access rel) { /* * Common layer to check that chroot doesn't ignore it (i.e. a chroot @@ -1373,7 +1562,7 @@ static void test_relative_path(struct __test_metadata *const _metadata, .path = TMP_DIR, .access = ACCESS_RO, }, - {} + {}, }; const struct rule layer2_subs[] = { { @@ -1384,7 +1573,7 @@ static void test_relative_path(struct __test_metadata *const _metadata, .path = dir_s2d2, .access = ACCESS_RO, }, - {} + {}, }; int dirfd, ruleset_fd; @@ -1425,14 +1614,16 @@ static void test_relative_path(struct __test_metadata *const _metadata, break; case REL_CHROOT_ONLY: /* Do chroot into dir_s1d2 (relative to dir_s2d2). */ - ASSERT_EQ(0, chroot("../../s1d1/s1d2")) { + ASSERT_EQ(0, chroot("../../s1d1/s1d2")) + { TH_LOG("Failed to chroot: %s", strerror(errno)); } dirfd = AT_FDCWD; break; case REL_CHROOT_CHDIR: /* Do chroot into dir_s1d2. */ - ASSERT_EQ(0, chroot(".")) { + ASSERT_EQ(0, chroot(".")) + { TH_LOG("Failed to chroot: %s", strerror(errno)); } dirfd = AT_FDCWD; @@ -1440,7 +1631,7 @@ static void test_relative_path(struct __test_metadata *const _metadata, } ASSERT_EQ((rel == REL_CHROOT_CHDIR) ? 0 : EACCES, - test_open_rel(dirfd, "..", O_RDONLY)); + test_open_rel(dirfd, "..", O_RDONLY)); ASSERT_EQ(0, test_open_rel(dirfd, ".", O_RDONLY)); if (rel == REL_CHROOT_ONLY) { @@ -1462,11 +1653,13 @@ static void test_relative_path(struct __test_metadata *const _metadata, if (rel != REL_CHROOT_CHDIR) { ASSERT_EQ(EACCES, test_open_rel(dirfd, "../../s1d1", O_RDONLY)); ASSERT_EQ(0, test_open_rel(dirfd, "../../s1d1/s1d2", O_RDONLY)); - ASSERT_EQ(0, test_open_rel(dirfd, "../../s1d1/s1d2/s1d3", O_RDONLY)); + ASSERT_EQ(0, test_open_rel(dirfd, "../../s1d1/s1d2/s1d3", + O_RDONLY)); ASSERT_EQ(EACCES, test_open_rel(dirfd, "../../s2d1", O_RDONLY)); ASSERT_EQ(0, test_open_rel(dirfd, "../../s2d1/s2d2", O_RDONLY)); - ASSERT_EQ(0, test_open_rel(dirfd, "../../s2d1/s2d2/s2d3", O_RDONLY)); + ASSERT_EQ(0, test_open_rel(dirfd, "../../s2d1/s2d2/s2d3", + O_RDONLY)); } if (rel == REL_OPEN) @@ -1495,40 +1688,42 @@ TEST_F_FORK(layout1, relative_chroot_chdir) } static void copy_binary(struct __test_metadata *const _metadata, - const char *const dst_path) + const char *const dst_path) { int dst_fd, src_fd; struct stat statbuf; dst_fd = open(dst_path, O_WRONLY | O_TRUNC | O_CLOEXEC); - ASSERT_LE(0, dst_fd) { - TH_LOG("Failed to open \"%s\": %s", dst_path, - strerror(errno)); + ASSERT_LE(0, dst_fd) + { + TH_LOG("Failed to open \"%s\": %s", dst_path, strerror(errno)); } src_fd = open(BINARY_PATH, O_RDONLY | O_CLOEXEC); - ASSERT_LE(0, src_fd) { + ASSERT_LE(0, src_fd) + { TH_LOG("Failed to open \"" BINARY_PATH "\": %s", - strerror(errno)); + strerror(errno)); } ASSERT_EQ(0, fstat(src_fd, &statbuf)); - ASSERT_EQ(statbuf.st_size, sendfile(dst_fd, src_fd, 0, - statbuf.st_size)); + ASSERT_EQ(statbuf.st_size, + sendfile(dst_fd, src_fd, 0, statbuf.st_size)); ASSERT_EQ(0, close(src_fd)); ASSERT_EQ(0, close(dst_fd)); } -static void test_execute(struct __test_metadata *const _metadata, - const int err, const char *const path) +static void test_execute(struct __test_metadata *const _metadata, const int err, + const char *const path) { int status; - char *const argv[] = {(char *)path, NULL}; + char *const argv[] = { (char *)path, NULL }; const pid_t child = fork(); ASSERT_LE(0, child); if (child == 0) { - ASSERT_EQ(err ? -1 : 0, execve(path, argv, NULL)) { + ASSERT_EQ(err ? -1 : 0, execve(path, argv, NULL)) + { TH_LOG("Failed to execute \"%s\": %s", path, - strerror(errno)); + strerror(errno)); }; ASSERT_EQ(err, errno); _exit(_metadata->passed ? 2 : 1); @@ -1536,9 +1731,10 @@ static void test_execute(struct __test_metadata *const _metadata, } ASSERT_EQ(child, waitpid(child, &status, 0)); ASSERT_EQ(1, WIFEXITED(status)); - ASSERT_EQ(err ? 2 : 0, WEXITSTATUS(status)) { + ASSERT_EQ(err ? 2 : 0, WEXITSTATUS(status)) + { TH_LOG("Unexpected return code for \"%s\": %s", path, - strerror(errno)); + strerror(errno)); }; } @@ -1549,10 +1745,10 @@ TEST_F_FORK(layout1, execute) .path = dir_s1d2, .access = LANDLOCK_ACCESS_FS_EXECUTE, }, - {} + {}, }; - const int ruleset_fd = create_ruleset(_metadata, rules[0].access, - rules); + const int ruleset_fd = + create_ruleset(_metadata, rules[0].access, rules); ASSERT_LE(0, ruleset_fd); copy_binary(_metadata, file1_s1d1); @@ -1577,15 +1773,21 @@ TEST_F_FORK(layout1, execute) TEST_F_FORK(layout1, link) { - const struct rule rules[] = { + const struct rule layer1[] = { { .path = dir_s1d2, .access = LANDLOCK_ACCESS_FS_MAKE_REG, }, - {} + {}, }; - const int ruleset_fd = create_ruleset(_metadata, rules[0].access, - rules); + const struct rule layer2[] = { + { + .path = dir_s1d3, + .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, + }, + {}, + }; + int ruleset_fd = create_ruleset(_metadata, layer1[0].access, layer1); ASSERT_LE(0, ruleset_fd); @@ -1598,14 +1800,30 @@ TEST_F_FORK(layout1, link) ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1)); ASSERT_EQ(EACCES, errno); + /* Denies linking because of reparenting. */ ASSERT_EQ(-1, link(file1_s2d1, file1_s1d2)); ASSERT_EQ(EXDEV, errno); ASSERT_EQ(-1, link(file2_s1d2, file1_s1d3)); ASSERT_EQ(EXDEV, errno); + ASSERT_EQ(-1, link(file2_s1d3, file1_s1d2)); + ASSERT_EQ(EXDEV, errno); ASSERT_EQ(0, link(file2_s1d2, file1_s1d2)); ASSERT_EQ(0, link(file2_s1d3, file1_s1d3)); + + /* Prepares for next unlinks. */ + ASSERT_EQ(0, unlink(file2_s1d2)); + ASSERT_EQ(0, unlink(file2_s1d3)); + + ruleset_fd = create_ruleset(_metadata, layer2[0].access, layer2); + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Checks that linkind doesn't require the ability to delete a file. */ + ASSERT_EQ(0, link(file1_s1d2, file2_s1d2)); + ASSERT_EQ(0, link(file1_s1d3, file2_s1d3)); } TEST_F_FORK(layout1, rename_file) @@ -1619,14 +1837,13 @@ TEST_F_FORK(layout1, rename_file) .path = dir_s2d2, .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, }, - {} + {}, }; - const int ruleset_fd = create_ruleset(_metadata, rules[0].access, - rules); + const int ruleset_fd = + create_ruleset(_metadata, rules[0].access, rules); ASSERT_LE(0, ruleset_fd); - ASSERT_EQ(0, unlink(file1_s1d1)); ASSERT_EQ(0, unlink(file1_s1d2)); enforce_ruleset(_metadata, ruleset_fd); @@ -1662,9 +1879,15 @@ TEST_F_FORK(layout1, rename_file) ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d2, AT_FDCWD, file1_s2d1, RENAME_EXCHANGE)); ASSERT_EQ(EACCES, errno); + /* Checks that file1_s2d1 cannot be removed (instead of ENOTDIR). */ + ASSERT_EQ(-1, rename(dir_s2d2, file1_s2d1)); + ASSERT_EQ(EACCES, errno); ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, dir_s2d2, RENAME_EXCHANGE)); ASSERT_EQ(EACCES, errno); + /* Checks that file1_s1d1 cannot be removed (instead of EISDIR). */ + ASSERT_EQ(-1, rename(file1_s1d1, dir_s1d2)); + ASSERT_EQ(EACCES, errno); /* Renames files with different parents. */ ASSERT_EQ(-1, rename(file1_s2d2, file1_s1d2)); @@ -1675,14 +1898,14 @@ TEST_F_FORK(layout1, rename_file) /* Exchanges and renames files with same parent. */ ASSERT_EQ(0, renameat2(AT_FDCWD, file2_s2d3, AT_FDCWD, file1_s2d3, - RENAME_EXCHANGE)); + RENAME_EXCHANGE)); ASSERT_EQ(0, rename(file2_s2d3, file1_s2d3)); /* Exchanges files and directories with same parent, twice. */ ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s2d3, - RENAME_EXCHANGE)); + RENAME_EXCHANGE)); ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s2d3, - RENAME_EXCHANGE)); + RENAME_EXCHANGE)); } TEST_F_FORK(layout1, rename_dir) @@ -1696,10 +1919,10 @@ TEST_F_FORK(layout1, rename_dir) .path = dir_s2d1, .access = LANDLOCK_ACCESS_FS_REMOVE_DIR, }, - {} + {}, }; - const int ruleset_fd = create_ruleset(_metadata, rules[0].access, - rules); + const int ruleset_fd = + create_ruleset(_metadata, rules[0].access, rules); ASSERT_LE(0, ruleset_fd); @@ -1727,22 +1950,743 @@ TEST_F_FORK(layout1, rename_dir) ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s1d1, AT_FDCWD, dir_s2d1, RENAME_EXCHANGE)); ASSERT_EQ(EACCES, errno); + /* Checks that dir_s1d2 cannot be removed (instead of ENOTDIR). */ + ASSERT_EQ(-1, rename(dir_s1d2, file1_s1d1)); + ASSERT_EQ(EACCES, errno); ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, dir_s1d2, RENAME_EXCHANGE)); ASSERT_EQ(EACCES, errno); + /* Checks that dir_s1d2 cannot be removed (instead of EISDIR). */ + ASSERT_EQ(-1, rename(file1_s1d1, dir_s1d2)); + ASSERT_EQ(EACCES, errno); /* * Exchanges and renames directory to the same parent, which allows * directory removal. */ ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, file1_s1d2, - RENAME_EXCHANGE)); + RENAME_EXCHANGE)); ASSERT_EQ(0, unlink(dir_s1d3)); ASSERT_EQ(0, mkdir(dir_s1d3, 0700)); ASSERT_EQ(0, rename(file1_s1d2, dir_s1d3)); ASSERT_EQ(0, rmdir(dir_s1d3)); } +TEST_F_FORK(layout1, reparent_refer) +{ + const struct rule layer1[] = { + { + .path = dir_s1d2, + .access = LANDLOCK_ACCESS_FS_REFER, + }, + { + .path = dir_s2d2, + .access = LANDLOCK_ACCESS_FS_REFER, + }, + {}, + }; + int ruleset_fd = + create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REFER, layer1); + + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d1)); + ASSERT_EQ(EXDEV, errno); + ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d2)); + ASSERT_EQ(EXDEV, errno); + ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d3)); + ASSERT_EQ(EXDEV, errno); + + ASSERT_EQ(-1, rename(dir_s1d3, dir_s2d1)); + ASSERT_EQ(EXDEV, errno); + ASSERT_EQ(-1, rename(dir_s1d3, dir_s2d2)); + ASSERT_EQ(EXDEV, errno); + /* + * Moving should only be allowed when the source and the destination + * parent directory have REFER. + */ + ASSERT_EQ(-1, rename(dir_s1d3, dir_s2d3)); + ASSERT_EQ(ENOTEMPTY, errno); + ASSERT_EQ(0, unlink(file1_s2d3)); + ASSERT_EQ(0, unlink(file2_s2d3)); + ASSERT_EQ(0, rename(dir_s1d3, dir_s2d3)); +} + +TEST_F_FORK(layout1, reparent_link) +{ + const struct rule layer1[] = { + { + .path = dir_s1d2, + .access = LANDLOCK_ACCESS_FS_MAKE_REG, + }, + { + .path = dir_s1d3, + .access = LANDLOCK_ACCESS_FS_REFER, + }, + { + .path = dir_s2d2, + .access = LANDLOCK_ACCESS_FS_REFER, + }, + { + .path = dir_s2d3, + .access = LANDLOCK_ACCESS_FS_MAKE_REG, + }, + {}, + }; + const int ruleset_fd = create_ruleset( + _metadata, + LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1); + + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + ASSERT_EQ(0, unlink(file1_s1d1)); + ASSERT_EQ(0, unlink(file1_s1d2)); + ASSERT_EQ(0, unlink(file1_s1d3)); + + /* Denies linking because of missing MAKE_REG. */ + ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1)); + ASSERT_EQ(EACCES, errno); + /* Denies linking because of missing source and destination REFER. */ + ASSERT_EQ(-1, link(file1_s2d1, file1_s1d2)); + ASSERT_EQ(EXDEV, errno); + /* Denies linking because of missing source REFER. */ + ASSERT_EQ(-1, link(file1_s2d1, file1_s1d3)); + ASSERT_EQ(EXDEV, errno); + + /* Denies linking because of missing MAKE_REG. */ + ASSERT_EQ(-1, link(file1_s2d2, file1_s1d1)); + ASSERT_EQ(EACCES, errno); + /* Denies linking because of missing destination REFER. */ + ASSERT_EQ(-1, link(file1_s2d2, file1_s1d2)); + ASSERT_EQ(EXDEV, errno); + + /* Allows linking because of REFER and MAKE_REG. */ + ASSERT_EQ(0, link(file1_s2d2, file1_s1d3)); + ASSERT_EQ(0, unlink(file1_s2d2)); + /* Reverse linking denied because of missing MAKE_REG. */ + ASSERT_EQ(-1, link(file1_s1d3, file1_s2d2)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(0, unlink(file1_s2d3)); + /* Checks reverse linking. */ + ASSERT_EQ(0, link(file1_s1d3, file1_s2d3)); + ASSERT_EQ(0, unlink(file1_s1d3)); + + /* + * This is OK for a file link, but it should not be allowed for a + * directory rename (because of the superset of access rights. + */ + ASSERT_EQ(0, link(file1_s2d3, file1_s1d3)); + ASSERT_EQ(0, unlink(file1_s1d3)); + + ASSERT_EQ(-1, link(file2_s1d2, file1_s1d3)); + ASSERT_EQ(EXDEV, errno); + ASSERT_EQ(-1, link(file2_s1d3, file1_s1d2)); + ASSERT_EQ(EXDEV, errno); + + ASSERT_EQ(0, link(file2_s1d2, file1_s1d2)); + ASSERT_EQ(0, link(file2_s1d3, file1_s1d3)); +} + +TEST_F_FORK(layout1, reparent_rename) +{ + /* Same rules as for reparent_link. */ + const struct rule layer1[] = { + { + .path = dir_s1d2, + .access = LANDLOCK_ACCESS_FS_MAKE_REG, + }, + { + .path = dir_s1d3, + .access = LANDLOCK_ACCESS_FS_REFER, + }, + { + .path = dir_s2d2, + .access = LANDLOCK_ACCESS_FS_REFER, + }, + { + .path = dir_s2d3, + .access = LANDLOCK_ACCESS_FS_MAKE_REG, + }, + {}, + }; + const int ruleset_fd = create_ruleset( + _metadata, + LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1); + + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + ASSERT_EQ(0, unlink(file1_s1d2)); + ASSERT_EQ(0, unlink(file1_s1d3)); + + /* Denies renaming because of missing MAKE_REG. */ + ASSERT_EQ(-1, renameat2(AT_FDCWD, file2_s1d1, AT_FDCWD, file1_s1d1, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file2_s1d1, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(0, unlink(file1_s1d1)); + ASSERT_EQ(-1, rename(file2_s1d1, file1_s1d1)); + ASSERT_EQ(EACCES, errno); + /* Even denies same file exchange. */ + ASSERT_EQ(-1, renameat2(AT_FDCWD, file2_s1d1, AT_FDCWD, file2_s1d1, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + + /* Denies renaming because of missing source and destination REFER. */ + ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d2)); + ASSERT_EQ(EXDEV, errno); + /* + * Denies renaming because of missing MAKE_REG, source and destination + * REFER. + */ + ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, file2_s1d1, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, renameat2(AT_FDCWD, file2_s1d1, AT_FDCWD, file1_s2d1, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + + /* Denies renaming because of missing source REFER. */ + ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d3)); + ASSERT_EQ(EXDEV, errno); + /* Denies renaming because of missing MAKE_REG. */ + ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, file2_s1d3, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + + /* Denies renaming because of missing MAKE_REG. */ + ASSERT_EQ(-1, rename(file1_s2d2, file1_s1d1)); + ASSERT_EQ(EACCES, errno); + /* Denies renaming because of missing destination REFER*/ + ASSERT_EQ(-1, rename(file1_s2d2, file1_s1d2)); + ASSERT_EQ(EXDEV, errno); + + /* Denies exchange because of one missing MAKE_REG. */ + ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, file2_s1d3, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + /* Allows renaming because of REFER and MAKE_REG. */ + ASSERT_EQ(0, rename(file1_s2d2, file1_s1d3)); + + /* Reverse renaming denied because of missing MAKE_REG. */ + ASSERT_EQ(-1, rename(file1_s1d3, file1_s2d2)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(0, unlink(file1_s2d3)); + ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3)); + + /* Tests reverse renaming. */ + ASSERT_EQ(0, rename(file1_s2d3, file1_s1d3)); + ASSERT_EQ(0, renameat2(AT_FDCWD, file2_s2d3, AT_FDCWD, file1_s1d3, + RENAME_EXCHANGE)); + ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3)); + + /* + * This is OK for a file rename, but it should not be allowed for a + * directory rename (because of the superset of access rights). + */ + ASSERT_EQ(0, rename(file1_s2d3, file1_s1d3)); + ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3)); + + /* + * Tests superset restrictions applied to directories. Not only the + * dir_s2d3's parent (dir_s2d2) should be taken into account but also + * access rights tied to dir_s2d3. dir_s2d2 is missing one access right + * compared to dir_s1d3/file1_s1d3 (MAKE_REG) but it is provided + * directly by the moved dir_s2d3. + */ + ASSERT_EQ(0, rename(dir_s2d3, file1_s1d3)); + ASSERT_EQ(0, rename(file1_s1d3, dir_s2d3)); + /* + * The first rename is allowed but not the exchange because dir_s1d3's + * parent (dir_s1d2) doesn't have REFER. + */ + ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, dir_s1d3, + RENAME_EXCHANGE)); + ASSERT_EQ(EXDEV, errno); + ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, file1_s2d3, + RENAME_EXCHANGE)); + ASSERT_EQ(EXDEV, errno); + ASSERT_EQ(-1, rename(file1_s2d3, dir_s1d3)); + ASSERT_EQ(EXDEV, errno); + + ASSERT_EQ(-1, rename(file2_s1d2, file1_s1d3)); + ASSERT_EQ(EXDEV, errno); + ASSERT_EQ(-1, rename(file2_s1d3, file1_s1d2)); + ASSERT_EQ(EXDEV, errno); + + /* Renaming in the same directory is always allowed. */ + ASSERT_EQ(0, rename(file2_s1d2, file1_s1d2)); + ASSERT_EQ(0, rename(file2_s1d3, file1_s1d3)); + + ASSERT_EQ(0, unlink(file1_s1d2)); + /* Denies because of missing source MAKE_REG and destination REFER. */ + ASSERT_EQ(-1, rename(dir_s2d3, file1_s1d2)); + ASSERT_EQ(EXDEV, errno); + + ASSERT_EQ(0, unlink(file1_s1d3)); + /* Denies because of missing source MAKE_REG and REFER. */ + ASSERT_EQ(-1, rename(dir_s2d2, file1_s1d3)); + ASSERT_EQ(EXDEV, errno); +} + +static void +reparent_exdev_layers_enforce1(struct __test_metadata *const _metadata) +{ + const struct rule layer1[] = { + { + .path = dir_s1d2, + .access = LANDLOCK_ACCESS_FS_REFER, + }, + { + /* Interesting for the layer2 tests. */ + .path = dir_s1d3, + .access = LANDLOCK_ACCESS_FS_MAKE_REG, + }, + { + .path = dir_s2d2, + .access = LANDLOCK_ACCESS_FS_REFER, + }, + { + .path = dir_s2d3, + .access = LANDLOCK_ACCESS_FS_MAKE_REG, + }, + {}, + }; + const int ruleset_fd = create_ruleset( + _metadata, + LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1); + + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); +} + +static void +reparent_exdev_layers_enforce2(struct __test_metadata *const _metadata) +{ + const struct rule layer2[] = { + { + .path = dir_s2d3, + .access = LANDLOCK_ACCESS_FS_MAKE_DIR, + }, + {}, + }; + /* + * Same checks as before but with a second layer and a new MAKE_DIR + * rule (and no explicit handling of REFER). + */ + const int ruleset_fd = + create_ruleset(_metadata, LANDLOCK_ACCESS_FS_MAKE_DIR, layer2); + + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); +} + +TEST_F_FORK(layout1, reparent_exdev_layers_rename1) +{ + ASSERT_EQ(0, unlink(file1_s2d2)); + ASSERT_EQ(0, unlink(file1_s2d3)); + + reparent_exdev_layers_enforce1(_metadata); + + /* + * Moving the dir_s1d3 directory below dir_s2d2 is allowed by Landlock + * because it doesn't inherit new access rights. + */ + ASSERT_EQ(0, rename(dir_s1d3, file1_s2d2)); + ASSERT_EQ(0, rename(file1_s2d2, dir_s1d3)); + + /* + * Moving the dir_s1d3 directory below dir_s2d3 is allowed, even if it + * gets a new inherited access rights (MAKE_REG), because MAKE_REG is + * already allowed for dir_s1d3. + */ + ASSERT_EQ(0, rename(dir_s1d3, file1_s2d3)); + ASSERT_EQ(0, rename(file1_s2d3, dir_s1d3)); + + /* + * However, moving the file1_s1d3 file below dir_s2d3 is allowed + * because it cannot inherit MAKE_REG right (which is dedicated to + * directories). + */ + ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3)); + + reparent_exdev_layers_enforce2(_metadata); + + /* + * Moving the dir_s1d3 directory below dir_s2d2 is now denied because + * MAKE_DIR is not tied to dir_s2d2. + */ + ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d2)); + ASSERT_EQ(EACCES, errno); + + /* + * Moving the dir_s1d3 directory below dir_s2d3 is forbidden because it + * would grants MAKE_REG and MAKE_DIR rights to it. + */ + ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d3)); + ASSERT_EQ(EXDEV, errno); + + /* + * However, moving the file2_s1d3 file below dir_s2d3 is allowed + * because it cannot inherit MAKE_REG nor MAKE_DIR rights (which are + * dedicated to directories). + */ + ASSERT_EQ(0, rename(file2_s1d3, file1_s2d3)); +} + +TEST_F_FORK(layout1, reparent_exdev_layers_rename2) +{ + reparent_exdev_layers_enforce1(_metadata); + + /* Checks EACCES predominance over EXDEV. */ + ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d2)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d2)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d3)); + ASSERT_EQ(EXDEV, errno); + /* Modify layout! */ + ASSERT_EQ(0, rename(file1_s1d2, file1_s2d3)); + + /* Without REFER source. */ + ASSERT_EQ(-1, rename(dir_s1d1, file1_s2d2)); + ASSERT_EQ(EXDEV, errno); + ASSERT_EQ(-1, rename(dir_s1d2, file1_s2d2)); + ASSERT_EQ(EXDEV, errno); + + reparent_exdev_layers_enforce2(_metadata); + + /* Checks EACCES predominance over EXDEV. */ + ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d2)); + ASSERT_EQ(EACCES, errno); + /* Checks with actual file2_s1d2. */ + ASSERT_EQ(-1, rename(file2_s1d2, file1_s2d2)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d3)); + ASSERT_EQ(EXDEV, errno); + /* Modify layout! */ + ASSERT_EQ(0, rename(file2_s1d2, file1_s2d3)); + + /* Without REFER source, EACCES wins over EXDEV. */ + ASSERT_EQ(-1, rename(dir_s1d1, file1_s2d2)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, rename(dir_s1d2, file1_s2d2)); + ASSERT_EQ(EACCES, errno); +} + +TEST_F_FORK(layout1, reparent_exdev_layers_exchange1) +{ + const char *const dir_file1_s1d2 = file1_s1d2, *const dir_file2_s2d3 = + file2_s2d3; + + ASSERT_EQ(0, unlink(file1_s1d2)); + ASSERT_EQ(0, mkdir(file1_s1d2, 0700)); + ASSERT_EQ(0, unlink(file2_s2d3)); + ASSERT_EQ(0, mkdir(file2_s2d3, 0700)); + + reparent_exdev_layers_enforce1(_metadata); + + /* Error predominance with file exchange: returns EXDEV and EACCES. */ + ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d3, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, file1_s1d1, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + + /* + * Checks with directories which creation could be allowed, but denied + * because of access rights that would be inherited. + */ + ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD, + dir_file2_s2d3, RENAME_EXCHANGE)); + ASSERT_EQ(EXDEV, errno); + ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, + dir_file1_s1d2, RENAME_EXCHANGE)); + ASSERT_EQ(EXDEV, errno); + + /* Checks with same access rights. */ + ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, dir_s2d3, + RENAME_EXCHANGE)); + ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_s1d3, + RENAME_EXCHANGE)); + + /* Checks with different (child-only) access rights. */ + ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_file1_s1d2, + RENAME_EXCHANGE)); + ASSERT_EQ(0, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD, dir_s2d3, + RENAME_EXCHANGE)); + + /* + * Checks that exchange between file and directory are consistent. + * + * Moving a file (file1_s2d2) to a directory which only grants more + * directory-related access rights is allowed, and at the same time + * moving a directory (dir_file2_s2d3) to another directory which + * grants less access rights is allowed too. + * + * See layout1.reparent_exdev_layers_exchange3 for inverted arguments. + */ + ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3, + RENAME_EXCHANGE)); + /* + * However, moving back the directory is denied because it would get + * more access rights than the current state and because file creation + * is forbidden (in dir_s2d2). + */ + ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + + reparent_exdev_layers_enforce2(_metadata); + + /* Error predominance with file exchange: returns EXDEV and EACCES. */ + ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d3, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, file1_s1d1, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + + /* Checks with directories which creation is now denied. */ + ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD, + dir_file2_s2d3, RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, + dir_file1_s1d2, RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + + /* Checks with different (child-only) access rights. */ + ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, dir_s2d3, + RENAME_EXCHANGE)); + /* Denied because of MAKE_DIR. */ + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_s1d3, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + + /* Checks with different (child-only) access rights. */ + ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_file1_s1d2, + RENAME_EXCHANGE)); + /* Denied because of MAKE_DIR. */ + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD, dir_s2d3, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + + /* See layout1.reparent_exdev_layers_exchange2 for complement. */ +} + +TEST_F_FORK(layout1, reparent_exdev_layers_exchange2) +{ + const char *const dir_file2_s2d3 = file2_s2d3; + + ASSERT_EQ(0, unlink(file2_s2d3)); + ASSERT_EQ(0, mkdir(file2_s2d3, 0700)); + + reparent_exdev_layers_enforce1(_metadata); + reparent_exdev_layers_enforce2(_metadata); + + /* Checks that exchange between file and directory are consistent. */ + ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); +} + +TEST_F_FORK(layout1, reparent_exdev_layers_exchange3) +{ + const char *const dir_file2_s2d3 = file2_s2d3; + + ASSERT_EQ(0, unlink(file2_s2d3)); + ASSERT_EQ(0, mkdir(file2_s2d3, 0700)); + + reparent_exdev_layers_enforce1(_metadata); + + /* + * Checks that exchange between file and directory are consistent, + * including with inverted arguments (see + * layout1.reparent_exdev_layers_exchange1). + */ + ASSERT_EQ(0, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2, + RENAME_EXCHANGE)); + ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); +} + +TEST_F_FORK(layout1, reparent_remove) +{ + const struct rule layer1[] = { + { + .path = dir_s1d1, + .access = LANDLOCK_ACCESS_FS_REFER | + LANDLOCK_ACCESS_FS_REMOVE_DIR, + }, + { + .path = dir_s1d2, + .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, + }, + { + .path = dir_s2d1, + .access = LANDLOCK_ACCESS_FS_REFER | + LANDLOCK_ACCESS_FS_REMOVE_FILE, + }, + {}, + }; + const int ruleset_fd = create_ruleset( + _metadata, + LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_REMOVE_DIR | + LANDLOCK_ACCESS_FS_REMOVE_FILE, + layer1); + + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Access denied because of wrong/swapped remove file/dir. */ + ASSERT_EQ(-1, rename(file1_s1d1, dir_s2d2)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, rename(dir_s2d2, file1_s1d1)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, dir_s2d2, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, dir_s2d3, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); + + /* Access allowed thanks to the matching rights. */ + ASSERT_EQ(-1, rename(file1_s2d1, dir_s1d2)); + ASSERT_EQ(EISDIR, errno); + ASSERT_EQ(-1, rename(dir_s1d2, file1_s2d1)); + ASSERT_EQ(ENOTDIR, errno); + ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d1)); + ASSERT_EQ(ENOTDIR, errno); + ASSERT_EQ(0, unlink(file1_s2d1)); + ASSERT_EQ(0, unlink(file1_s1d3)); + ASSERT_EQ(0, unlink(file2_s1d3)); + ASSERT_EQ(0, rename(dir_s1d3, file1_s2d1)); + + /* Effectively removes a file and a directory by exchanging them. */ + ASSERT_EQ(0, mkdir(dir_s1d3, 0700)); + ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s1d3, + RENAME_EXCHANGE)); + ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s1d3, + RENAME_EXCHANGE)); + ASSERT_EQ(EACCES, errno); +} + +TEST_F_FORK(layout1, reparent_dom_superset) +{ + const struct rule layer1[] = { + { + .path = dir_s1d2, + .access = LANDLOCK_ACCESS_FS_REFER, + }, + { + .path = file1_s1d2, + .access = LANDLOCK_ACCESS_FS_EXECUTE, + }, + { + .path = dir_s1d3, + .access = LANDLOCK_ACCESS_FS_MAKE_SOCK | + LANDLOCK_ACCESS_FS_EXECUTE, + }, + { + .path = dir_s2d2, + .access = LANDLOCK_ACCESS_FS_REFER | + LANDLOCK_ACCESS_FS_EXECUTE | + LANDLOCK_ACCESS_FS_MAKE_SOCK, + }, + { + .path = dir_s2d3, + .access = LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_MAKE_FIFO, + }, + {}, + }; + int ruleset_fd = create_ruleset(_metadata, + LANDLOCK_ACCESS_FS_REFER | + LANDLOCK_ACCESS_FS_EXECUTE | + LANDLOCK_ACCESS_FS_MAKE_SOCK | + LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_MAKE_FIFO, + layer1); + + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d1)); + ASSERT_EQ(EXDEV, errno); + /* + * Moving file1_s1d2 beneath dir_s2d3 would grant it the READ_FILE + * access right. + */ + ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d3)); + ASSERT_EQ(EXDEV, errno); + /* + * Moving file1_s1d2 should be allowed even if dir_s2d2 grants a + * superset of access rights compared to dir_s1d2, because file1_s1d2 + * already has these access rights anyway. + */ + ASSERT_EQ(0, rename(file1_s1d2, file1_s2d2)); + ASSERT_EQ(0, rename(file1_s2d2, file1_s1d2)); + + ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d1)); + ASSERT_EQ(EXDEV, errno); + /* + * Moving dir_s1d3 beneath dir_s2d3 would grant it the MAKE_FIFO access + * right. + */ + ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d3)); + ASSERT_EQ(EXDEV, errno); + /* + * Moving dir_s1d3 should be allowed even if dir_s2d2 grants a superset + * of access rights compared to dir_s1d2, because dir_s1d3 already has + * these access rights anyway. + */ + ASSERT_EQ(0, rename(dir_s1d3, file1_s2d2)); + ASSERT_EQ(0, rename(file1_s2d2, dir_s1d3)); + + /* + * Moving file1_s2d3 beneath dir_s1d2 is allowed, but moving it back + * will be denied because the new inherited access rights from dir_s1d2 + * will be less than the destination (original) dir_s2d3. This is a + * sinkhole scenario where we cannot move back files or directories. + */ + ASSERT_EQ(0, rename(file1_s2d3, file2_s1d2)); + ASSERT_EQ(-1, rename(file2_s1d2, file1_s2d3)); + ASSERT_EQ(EXDEV, errno); + ASSERT_EQ(0, unlink(file2_s1d2)); + ASSERT_EQ(0, unlink(file2_s2d3)); + /* + * Checks similar directory one-way move: dir_s2d3 loses EXECUTE and + * MAKE_SOCK which were inherited from dir_s1d3. + */ + ASSERT_EQ(0, rename(dir_s2d3, file2_s1d2)); + ASSERT_EQ(-1, rename(file2_s1d2, dir_s2d3)); + ASSERT_EQ(EXDEV, errno); +} + TEST_F_FORK(layout1, remove_dir) { const struct rule rules[] = { @@ -1750,10 +2694,10 @@ TEST_F_FORK(layout1, remove_dir) .path = dir_s1d2, .access = LANDLOCK_ACCESS_FS_REMOVE_DIR, }, - {} + {}, }; - const int ruleset_fd = create_ruleset(_metadata, rules[0].access, - rules); + const int ruleset_fd = + create_ruleset(_metadata, rules[0].access, rules); ASSERT_LE(0, ruleset_fd); @@ -1787,10 +2731,10 @@ TEST_F_FORK(layout1, remove_file) .path = dir_s1d2, .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, }, - {} + {}, }; - const int ruleset_fd = create_ruleset(_metadata, rules[0].access, - rules); + const int ruleset_fd = + create_ruleset(_metadata, rules[0].access, rules); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); @@ -1805,14 +2749,15 @@ TEST_F_FORK(layout1, remove_file) } static void test_make_file(struct __test_metadata *const _metadata, - const __u64 access, const mode_t mode, const dev_t dev) + const __u64 access, const mode_t mode, + const dev_t dev) { const struct rule rules[] = { { .path = dir_s1d2, .access = access, }, - {} + {}, }; const int ruleset_fd = create_ruleset(_metadata, access, rules); @@ -1820,9 +2765,10 @@ static void test_make_file(struct __test_metadata *const _metadata, ASSERT_EQ(0, unlink(file1_s1d1)); ASSERT_EQ(0, unlink(file2_s1d1)); - ASSERT_EQ(0, mknod(file2_s1d1, mode | 0400, dev)) { - TH_LOG("Failed to make file \"%s\": %s", - file2_s1d1, strerror(errno)); + ASSERT_EQ(0, mknod(file2_s1d1, mode | 0400, dev)) + { + TH_LOG("Failed to make file \"%s\": %s", file2_s1d1, + strerror(errno)); }; ASSERT_EQ(0, unlink(file1_s1d2)); @@ -1841,9 +2787,10 @@ static void test_make_file(struct __test_metadata *const _metadata, ASSERT_EQ(-1, rename(file2_s1d1, file1_s1d1)); ASSERT_EQ(EACCES, errno); - ASSERT_EQ(0, mknod(file1_s1d2, mode | 0400, dev)) { - TH_LOG("Failed to make file \"%s\": %s", - file1_s1d2, strerror(errno)); + ASSERT_EQ(0, mknod(file1_s1d2, mode | 0400, dev)) + { + TH_LOG("Failed to make file \"%s\": %s", file1_s1d2, + strerror(errno)); }; ASSERT_EQ(0, link(file1_s1d2, file2_s1d2)); ASSERT_EQ(0, unlink(file2_s1d2)); @@ -1860,7 +2807,7 @@ TEST_F_FORK(layout1, make_char) /* Creates a /dev/null device. */ set_cap(_metadata, CAP_MKNOD); test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_CHAR, S_IFCHR, - makedev(1, 3)); + makedev(1, 3)); } TEST_F_FORK(layout1, make_block) @@ -1868,7 +2815,7 @@ TEST_F_FORK(layout1, make_block) /* Creates a /dev/loop0 device. */ set_cap(_metadata, CAP_MKNOD); test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_BLOCK, S_IFBLK, - makedev(7, 0)); + makedev(7, 0)); } TEST_F_FORK(layout1, make_reg_1) @@ -1898,10 +2845,10 @@ TEST_F_FORK(layout1, make_sym) .path = dir_s1d2, .access = LANDLOCK_ACCESS_FS_MAKE_SYM, }, - {} + {}, }; - const int ruleset_fd = create_ruleset(_metadata, rules[0].access, - rules); + const int ruleset_fd = + create_ruleset(_metadata, rules[0].access, rules); ASSERT_LE(0, ruleset_fd); @@ -1943,10 +2890,10 @@ TEST_F_FORK(layout1, make_dir) .path = dir_s1d2, .access = LANDLOCK_ACCESS_FS_MAKE_DIR, }, - {} + {}, }; - const int ruleset_fd = create_ruleset(_metadata, rules[0].access, - rules); + const int ruleset_fd = + create_ruleset(_metadata, rules[0].access, rules); ASSERT_LE(0, ruleset_fd); @@ -1965,12 +2912,12 @@ TEST_F_FORK(layout1, make_dir) } static int open_proc_fd(struct __test_metadata *const _metadata, const int fd, - const int open_flags) + const int open_flags) { static const char path_template[] = "/proc/self/fd/%d"; char procfd_path[sizeof(path_template) + 10]; - const int procfd_path_size = snprintf(procfd_path, sizeof(procfd_path), - path_template, fd); + const int procfd_path_size = + snprintf(procfd_path, sizeof(procfd_path), path_template, fd); ASSERT_LT(procfd_path_size, sizeof(procfd_path)); return open(procfd_path, open_flags); @@ -1983,12 +2930,13 @@ TEST_F_FORK(layout1, proc_unlinked_file) .path = file1_s1d2, .access = LANDLOCK_ACCESS_FS_READ_FILE, }, - {} + {}, }; int reg_fd, proc_fd; - const int ruleset_fd = create_ruleset(_metadata, - LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, rules); + const int ruleset_fd = create_ruleset( + _metadata, + LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE, + rules); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); @@ -2005,9 +2953,10 @@ TEST_F_FORK(layout1, proc_unlinked_file) ASSERT_EQ(0, close(proc_fd)); proc_fd = open_proc_fd(_metadata, reg_fd, O_RDWR | O_CLOEXEC); - ASSERT_EQ(-1, proc_fd) { - TH_LOG("Successfully opened /proc/self/fd/%d: %s", - reg_fd, strerror(errno)); + ASSERT_EQ(-1, proc_fd) + { + TH_LOG("Successfully opened /proc/self/fd/%d: %s", reg_fd, + strerror(errno)); } ASSERT_EQ(EACCES, errno); @@ -2023,13 +2972,13 @@ TEST_F_FORK(layout1, proc_pipe) { .path = dir_s1d2, .access = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, + LANDLOCK_ACCESS_FS_WRITE_FILE, }, - {} + {}, }; /* Limits read and write access to files tied to the filesystem. */ - const int ruleset_fd = create_ruleset(_metadata, rules[0].access, - rules); + const int ruleset_fd = + create_ruleset(_metadata, rules[0].access, rules); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); @@ -2041,7 +2990,8 @@ TEST_F_FORK(layout1, proc_pipe) /* Checks access to pipes through FD. */ ASSERT_EQ(0, pipe2(pipe_fds, O_CLOEXEC)); - ASSERT_EQ(1, write(pipe_fds[1], ".", 1)) { + ASSERT_EQ(1, write(pipe_fds[1], ".", 1)) + { TH_LOG("Failed to write in pipe: %s", strerror(errno)); } ASSERT_EQ(1, read(pipe_fds[0], &buf, 1)); @@ -2050,9 +3000,10 @@ TEST_F_FORK(layout1, proc_pipe) /* Checks write access to pipe through /proc/self/fd . */ proc_fd = open_proc_fd(_metadata, pipe_fds[1], O_WRONLY | O_CLOEXEC); ASSERT_LE(0, proc_fd); - ASSERT_EQ(1, write(proc_fd, ".", 1)) { + ASSERT_EQ(1, write(proc_fd, ".", 1)) + { TH_LOG("Failed to write through /proc/self/fd/%d: %s", - pipe_fds[1], strerror(errno)); + pipe_fds[1], strerror(errno)); } ASSERT_EQ(0, close(proc_fd)); @@ -2060,9 +3011,10 @@ TEST_F_FORK(layout1, proc_pipe) proc_fd = open_proc_fd(_metadata, pipe_fds[0], O_RDONLY | O_CLOEXEC); ASSERT_LE(0, proc_fd); buf = '\0'; - ASSERT_EQ(1, read(proc_fd, &buf, 1)) { + ASSERT_EQ(1, read(proc_fd, &buf, 1)) + { TH_LOG("Failed to read through /proc/self/fd/%d: %s", - pipe_fds[1], strerror(errno)); + pipe_fds[1], strerror(errno)); } ASSERT_EQ(0, close(proc_fd)); @@ -2070,8 +3022,9 @@ TEST_F_FORK(layout1, proc_pipe) ASSERT_EQ(0, close(pipe_fds[1])); } -FIXTURE(layout1_bind) { -}; +/* clang-format off */ +FIXTURE(layout1_bind) {}; +/* clang-format on */ FIXTURE_SETUP(layout1_bind) { @@ -2161,7 +3114,7 @@ TEST_F_FORK(layout1_bind, same_content_same_file) .path = dir_s2d1, .access = ACCESS_RW, }, - {} + {}, }; /* * Sets access rights on the same bind-mounted directories. The result @@ -2177,7 +3130,7 @@ TEST_F_FORK(layout1_bind, same_content_same_file) .path = dir_s2d2, .access = ACCESS_RW, }, - {} + {}, }; /* Only allow read-access to the s1d3 hierarchies. */ const struct rule layer3_source[] = { @@ -2185,7 +3138,7 @@ TEST_F_FORK(layout1_bind, same_content_same_file) .path = dir_s1d3, .access = LANDLOCK_ACCESS_FS_READ_FILE, }, - {} + {}, }; /* Removes all access rights. */ const struct rule layer4_destination[] = { @@ -2193,7 +3146,7 @@ TEST_F_FORK(layout1_bind, same_content_same_file) .path = bind_file1_s1d3, .access = LANDLOCK_ACCESS_FS_WRITE_FILE, }, - {} + {}, }; int ruleset_fd; @@ -2282,8 +3235,46 @@ TEST_F_FORK(layout1_bind, same_content_same_file) ASSERT_EQ(EACCES, test_open(bind_file1_s1d3, O_WRONLY)); } -#define LOWER_BASE TMP_DIR "/lower" -#define LOWER_DATA LOWER_BASE "/data" +TEST_F_FORK(layout1_bind, reparent_cross_mount) +{ + const struct rule layer1[] = { + { + /* dir_s2d1 is beneath the dir_s2d2 mount point. */ + .path = dir_s2d1, + .access = LANDLOCK_ACCESS_FS_REFER, + }, + { + .path = bind_dir_s1d3, + .access = LANDLOCK_ACCESS_FS_EXECUTE, + }, + {}, + }; + int ruleset_fd = create_ruleset( + _metadata, + LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_EXECUTE, layer1); + + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Checks basic denied move. */ + ASSERT_EQ(-1, rename(file1_s1d1, file1_s1d2)); + ASSERT_EQ(EXDEV, errno); + + /* Checks real cross-mount move (Landlock is not involved). */ + ASSERT_EQ(-1, rename(file1_s2d1, file1_s2d2)); + ASSERT_EQ(EXDEV, errno); + + /* Checks move that will give more accesses. */ + ASSERT_EQ(-1, rename(file1_s2d2, bind_file1_s1d3)); + ASSERT_EQ(EXDEV, errno); + + /* Checks legitimate downgrade move. */ + ASSERT_EQ(0, rename(bind_file1_s1d3, file1_s2d2)); +} + +#define LOWER_BASE TMP_DIR "/lower" +#define LOWER_DATA LOWER_BASE "/data" static const char lower_fl1[] = LOWER_DATA "/fl1"; static const char lower_dl1[] = LOWER_DATA "/dl1"; static const char lower_dl1_fl2[] = LOWER_DATA "/dl1/fl2"; @@ -2295,23 +3286,23 @@ static const char lower_do1_fl3[] = LOWER_DATA "/do1/fl3"; static const char (*lower_base_files[])[] = { &lower_fl1, &lower_fo1, - NULL + NULL, }; static const char (*lower_base_directories[])[] = { &lower_dl1, &lower_do1, - NULL + NULL, }; static const char (*lower_sub_files[])[] = { &lower_dl1_fl2, &lower_do1_fo2, &lower_do1_fl3, - NULL + NULL, }; -#define UPPER_BASE TMP_DIR "/upper" -#define UPPER_DATA UPPER_BASE "/data" -#define UPPER_WORK UPPER_BASE "/work" +#define UPPER_BASE TMP_DIR "/upper" +#define UPPER_DATA UPPER_BASE "/data" +#define UPPER_WORK UPPER_BASE "/work" static const char upper_fu1[] = UPPER_DATA "/fu1"; static const char upper_du1[] = UPPER_DATA "/du1"; static const char upper_du1_fu2[] = UPPER_DATA "/du1/fu2"; @@ -2323,22 +3314,22 @@ static const char upper_do1_fu3[] = UPPER_DATA "/do1/fu3"; static const char (*upper_base_files[])[] = { &upper_fu1, &upper_fo1, - NULL + NULL, }; static const char (*upper_base_directories[])[] = { &upper_du1, &upper_do1, - NULL + NULL, }; static const char (*upper_sub_files[])[] = { &upper_du1_fu2, &upper_do1_fo2, &upper_do1_fu3, - NULL + NULL, }; -#define MERGE_BASE TMP_DIR "/merge" -#define MERGE_DATA MERGE_BASE "/data" +#define MERGE_BASE TMP_DIR "/merge" +#define MERGE_DATA MERGE_BASE "/data" static const char merge_fl1[] = MERGE_DATA "/fl1"; static const char merge_dl1[] = MERGE_DATA "/dl1"; static const char merge_dl1_fl2[] = MERGE_DATA "/dl1/fl2"; @@ -2355,21 +3346,17 @@ static const char (*merge_base_files[])[] = { &merge_fl1, &merge_fu1, &merge_fo1, - NULL + NULL, }; static const char (*merge_base_directories[])[] = { &merge_dl1, &merge_du1, &merge_do1, - NULL + NULL, }; static const char (*merge_sub_files[])[] = { - &merge_dl1_fl2, - &merge_du1_fu2, - &merge_do1_fo2, - &merge_do1_fl3, - &merge_do1_fu3, - NULL + &merge_dl1_fl2, &merge_du1_fu2, &merge_do1_fo2, + &merge_do1_fl3, &merge_do1_fu3, NULL, }; /* @@ -2411,8 +3398,9 @@ static const char (*merge_sub_files[])[] = { * └── work */ -FIXTURE(layout2_overlay) { -}; +/* clang-format off */ +FIXTURE(layout2_overlay) {}; +/* clang-format on */ FIXTURE_SETUP(layout2_overlay) { @@ -2444,9 +3432,8 @@ FIXTURE_SETUP(layout2_overlay) set_cap(_metadata, CAP_SYS_ADMIN); set_cap(_metadata, CAP_DAC_OVERRIDE); ASSERT_EQ(0, mount("overlay", MERGE_DATA, "overlay", 0, - "lowerdir=" LOWER_DATA - ",upperdir=" UPPER_DATA - ",workdir=" UPPER_WORK)); + "lowerdir=" LOWER_DATA ",upperdir=" UPPER_DATA + ",workdir=" UPPER_WORK)); clear_cap(_metadata, CAP_DAC_OVERRIDE); clear_cap(_metadata, CAP_SYS_ADMIN); } @@ -2513,9 +3500,9 @@ TEST_F_FORK(layout2_overlay, no_restriction) ASSERT_EQ(0, test_open(merge_do1_fu3, O_RDONLY)); } -#define for_each_path(path_list, path_entry, i) \ - for (i = 0, path_entry = *path_list[i]; path_list[i]; \ - path_entry = *path_list[++i]) +#define for_each_path(path_list, path_entry, i) \ + for (i = 0, path_entry = *path_list[i]; path_list[i]; \ + path_entry = *path_list[++i]) TEST_F_FORK(layout2_overlay, same_content_different_file) { @@ -2533,7 +3520,7 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) .path = MERGE_BASE, .access = ACCESS_RW, }, - {} + {}, }; const struct rule layer2_data[] = { { @@ -2548,7 +3535,7 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) .path = MERGE_DATA, .access = ACCESS_RW, }, - {} + {}, }; /* Sets access right on directories inside both layers. */ const struct rule layer3_subdirs[] = { @@ -2580,7 +3567,7 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) .path = merge_do1, .access = ACCESS_RW, }, - {} + {}, }; /* Tighten access rights to the files. */ const struct rule layer4_files[] = { @@ -2611,37 +3598,37 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) { .path = merge_dl1_fl2, .access = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, + LANDLOCK_ACCESS_FS_WRITE_FILE, }, { .path = merge_du1_fu2, .access = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, + LANDLOCK_ACCESS_FS_WRITE_FILE, }, { .path = merge_do1_fo2, .access = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, + LANDLOCK_ACCESS_FS_WRITE_FILE, }, { .path = merge_do1_fl3, .access = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, + LANDLOCK_ACCESS_FS_WRITE_FILE, }, { .path = merge_do1_fu3, .access = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, + LANDLOCK_ACCESS_FS_WRITE_FILE, }, - {} + {}, }; const struct rule layer5_merge_only[] = { { .path = MERGE_DATA, .access = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, + LANDLOCK_ACCESS_FS_WRITE_FILE, }, - {} + {}, }; int ruleset_fd; size_t i; @@ -2659,7 +3646,8 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); } for_each_path(lower_base_directories, path_entry, i) { - ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY | O_DIRECTORY)); + ASSERT_EQ(EACCES, + test_open(path_entry, O_RDONLY | O_DIRECTORY)); } for_each_path(lower_sub_files, path_entry, i) { ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); @@ -2671,7 +3659,8 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); } for_each_path(upper_base_directories, path_entry, i) { - ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY | O_DIRECTORY)); + ASSERT_EQ(EACCES, + test_open(path_entry, O_RDONLY | O_DIRECTORY)); } for_each_path(upper_sub_files, path_entry, i) { ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); @@ -2756,7 +3745,8 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) ASSERT_EQ(EACCES, test_open(path_entry, O_RDWR)); } for_each_path(merge_base_directories, path_entry, i) { - ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY | O_DIRECTORY)); + ASSERT_EQ(EACCES, + test_open(path_entry, O_RDONLY | O_DIRECTORY)); } for_each_path(merge_sub_files, path_entry, i) { ASSERT_EQ(0, test_open(path_entry, O_RDWR)); @@ -2781,7 +3771,8 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) ASSERT_EQ(EACCES, test_open(path_entry, O_RDWR)); } for_each_path(merge_base_directories, path_entry, i) { - ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY | O_DIRECTORY)); + ASSERT_EQ(EACCES, + test_open(path_entry, O_RDONLY | O_DIRECTORY)); } for_each_path(merge_sub_files, path_entry, i) { ASSERT_EQ(0, test_open(path_entry, O_RDWR)); diff --git a/tools/testing/selftests/landlock/ptrace_test.c b/tools/testing/selftests/landlock/ptrace_test.c index 15fbef9cc849..c28ef98ff3ac 100644 --- a/tools/testing/selftests/landlock/ptrace_test.c +++ b/tools/testing/selftests/landlock/ptrace_test.c @@ -26,9 +26,10 @@ static void create_domain(struct __test_metadata *const _metadata) .handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_BLOCK, }; - ruleset_fd = landlock_create_ruleset(&ruleset_attr, - sizeof(ruleset_attr), 0); - EXPECT_LE(0, ruleset_fd) { + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + EXPECT_LE(0, ruleset_fd) + { TH_LOG("Failed to create a ruleset: %s", strerror(errno)); } EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); @@ -43,7 +44,7 @@ static int test_ptrace_read(const pid_t pid) int procenv_path_size, fd; procenv_path_size = snprintf(procenv_path, sizeof(procenv_path), - path_template, pid); + path_template, pid); if (procenv_path_size >= sizeof(procenv_path)) return E2BIG; @@ -59,9 +60,12 @@ static int test_ptrace_read(const pid_t pid) return 0; } -FIXTURE(hierarchy) { }; +/* clang-format off */ +FIXTURE(hierarchy) {}; +/* clang-format on */ -FIXTURE_VARIANT(hierarchy) { +FIXTURE_VARIANT(hierarchy) +{ const bool domain_both; const bool domain_parent; const bool domain_child; @@ -83,7 +87,9 @@ FIXTURE_VARIANT(hierarchy) { * \ P2 -> P1 : allow * 'P2 */ +/* clang-format off */ FIXTURE_VARIANT_ADD(hierarchy, allow_without_domain) { + /* clang-format on */ .domain_both = false, .domain_parent = false, .domain_child = false, @@ -98,7 +104,9 @@ FIXTURE_VARIANT_ADD(hierarchy, allow_without_domain) { * | P2 | * '------' */ +/* clang-format off */ FIXTURE_VARIANT_ADD(hierarchy, allow_with_one_domain) { + /* clang-format on */ .domain_both = false, .domain_parent = false, .domain_child = true, @@ -112,7 +120,9 @@ FIXTURE_VARIANT_ADD(hierarchy, allow_with_one_domain) { * ' * P2 */ +/* clang-format off */ FIXTURE_VARIANT_ADD(hierarchy, deny_with_parent_domain) { + /* clang-format on */ .domain_both = false, .domain_parent = true, .domain_child = false, @@ -127,7 +137,9 @@ FIXTURE_VARIANT_ADD(hierarchy, deny_with_parent_domain) { * | P2 | * '------' */ +/* clang-format off */ FIXTURE_VARIANT_ADD(hierarchy, deny_with_sibling_domain) { + /* clang-format on */ .domain_both = false, .domain_parent = true, .domain_child = true, @@ -142,7 +154,9 @@ FIXTURE_VARIANT_ADD(hierarchy, deny_with_sibling_domain) { * | P2 | * '-------------' */ +/* clang-format off */ FIXTURE_VARIANT_ADD(hierarchy, allow_sibling_domain) { + /* clang-format on */ .domain_both = true, .domain_parent = false, .domain_child = false, @@ -158,7 +172,9 @@ FIXTURE_VARIANT_ADD(hierarchy, allow_sibling_domain) { * | '------' | * '-----------------' */ +/* clang-format off */ FIXTURE_VARIANT_ADD(hierarchy, allow_with_nested_domain) { + /* clang-format on */ .domain_both = true, .domain_parent = false, .domain_child = true, @@ -174,7 +190,9 @@ FIXTURE_VARIANT_ADD(hierarchy, allow_with_nested_domain) { * | P2 | * '-----------------' */ +/* clang-format off */ FIXTURE_VARIANT_ADD(hierarchy, deny_with_nested_and_parent_domain) { + /* clang-format on */ .domain_both = true, .domain_parent = true, .domain_child = false, @@ -192,17 +210,21 @@ FIXTURE_VARIANT_ADD(hierarchy, deny_with_nested_and_parent_domain) { * | '------' | * '-----------------' */ +/* clang-format off */ FIXTURE_VARIANT_ADD(hierarchy, deny_with_forked_domain) { + /* clang-format on */ .domain_both = true, .domain_parent = true, .domain_child = true, }; FIXTURE_SETUP(hierarchy) -{ } +{ +} FIXTURE_TEARDOWN(hierarchy) -{ } +{ +} /* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */ TEST_F(hierarchy, trace) @@ -330,7 +352,7 @@ TEST_F(hierarchy, trace) ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); ASSERT_EQ(child, waitpid(child, &status, 0)); if (WIFSIGNALED(status) || !WIFEXITED(status) || - WEXITSTATUS(status) != EXIT_SUCCESS) + WEXITSTATUS(status) != EXIT_SUCCESS) _metadata->passed = 0; }