diff -Nru slimbook-qc71-0.1/common.postinst slimbook-qc71-0.2/common.postinst --- slimbook-qc71-0.1/common.postinst 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/common.postinst 2022-08-18 13:19:13.000000000 +0000 @@ -0,0 +1,294 @@ +#!/bin/sh +# Copyright (C) 2002-2005 Flavio Stanchina +# Copyright (C) 2005-2006 Aric Cyr +# Copyright (C) 2007 Mario Limonciello +# Copyright (C) 2009 Alberto Milone + +set -e + +. /usr/share/debconf/confmodule + +uname_s=$(uname -s) + +_get_kernel_dir() { + KVER=$1 + case ${uname_s} in + Linux) DIR="/lib/modules/$KVER/build" ;; + GNU/kFreeBSD) DIR="/usr/src/kfreebsd-headers-$KVER/sys" ;; + esac + echo $DIR +} + +_check_kernel_dir() { + DIR=$(_get_kernel_dir $1) + case ${uname_s} in + Linux) test -e $DIR/include ;; + GNU/kFreeBSD) test -e $DIR/kern && test -e $DIR/conf/kmod.mk ;; + *) return 1 ;; + esac + return $? +} + +# Check the existence of a kernel named as $1 +_is_kernel_name_correct() { + CORRECT="no" + KERNEL_NAME=$1 + + for kernel in /boot/config-*; do + [ -f "$kernel" ] || continue + KERNEL=${kernel#*-} + if [ "${KERNEL}" = "${KERNEL_NAME}" ]; then + CORRECT="yes" + break + fi + done + + echo $CORRECT +} + + +# Get the most recent kernel on Debian based systems. This keeps +# into account both the version and the ABI. If the current kernel +# is the most recent kernel then the function will print a null string. +_get_newest_kernel_debian() { + NEWEST_KERNEL= + NEWEST_VERSION= + NEWEST_ABI= + + for kernel in /boot/config-*; do + [ -f "$kernel" ] || continue + KERNEL=${kernel#*-} + KERNEL_VERSION=${KERNEL%%-*} + ABI=${KERNEL#*-} + ABI=${ABI%%-*} + + if [ -z "$NEWEST_KERNEL" ]; then + # The 1st time get a version which is bigger than $1 + COMPARE_TO=$1 + else + # Get the biggest version + COMPARE_TO="$NEWEST_VERSION-$NEWEST_ABI" + fi + + # if $kernel is greater than $COMPARE_TO + if [ `dpkg --compare-versions "$KERNEL_VERSION-$ABI" ge "$COMPARE_TO" && echo "yes" || \ + echo "no"` = "yes" ]; then + NEWEST_KERNEL=$KERNEL + NEWEST_VERSION=$KERNEL_VERSION + NEWEST_ABI=$ABI + fi + done + + echo "$NEWEST_KERNEL" +} + +# Get the most recent kernel in Rhel based systems. If the current kernel +# is the most recent kernel then the function will print a null string. +_get_newest_kernel_rhel() { + NEWEST_KERNEL= + + LAST_INSTALLED_KERNEL=$(rpm -q --whatprovides kernel --last | grep kernel -m1 | cut -f1 -d' ') + + LIK_FORMATTED_NAME=$(rpm -q $LAST_INSTALLED_KERNEL --queryformat="%{VERSION}-%{RELEASE}.%{ARCH}\n") + + if [ `echo $LIK_FORMATTED_NAME | grep 2.6 >/dev/null` ]; then + # Fedora and Suse + NEWEST_KERNEL=$LIK_FORMATTED_NAME + else + # Hack for Mandriva where $LIK_FORMATTED_NAME is broken + LIK_NAME=$(rpm -q $LAST_INSTALLED_KERNEL --queryformat="%{NAME}\n") + LIK_TYPE=${LIK_NAME#kernel-} + LIK_TYPE=${LIK_TYPE%%-*} + LIK_STRIPPED=${LIK_NAME#kernel-} + LIK_STRIPPED=${LIK_STRIPPED#$LIK_TYPE-} + LIK_STRIPPED_BASE=${LIK_STRIPPED%%-*} + LIK_STRIPPED_END=${LIK_STRIPPED#$LIK_STRIPPED_BASE-} + LIK_FINAL=$LIK_STRIPPED_BASE-$LIK_TYPE-$LIK_STRIPPED_END + + NEWEST_KERNEL=$LIK_FINAL + fi + + echo $NEWEST_KERNEL +} + +# Get the newest kernel on Debian and Rhel based systems. +get_newest_kernel() { + NEWEST_KERNEL= + # Try Debian first as rpm can be installed in Debian based distros + if [ -e /usr/bin/dpkg ]; then + # If DEB based + CURRENT_VERSION=${CURRENT_KERNEL%%-*} + CURRENT_ABI=${CURRENT_KERNEL#*-} + CURRENT_FLAVOUR=${CURRENT_ABI#*-} + CURRENT_ABI=${CURRENT_ABI%%-*} + NEWEST_KERNEL=$(_get_newest_kernel_debian "$CURRENT_VERSION-$CURRENT_ABI") + + elif [ `which rpm >/dev/null` ]; then + # If RPM based + NEWEST_KERNEL=$(_get_newest_kernel_rhel) + fi + + # Make sure that kernel name that we extracted corresponds to an installed + # kernel + if [ -n "$NEWEST_KERNEL" ] && [ `_is_kernel_name_correct $NEWEST_KERNEL` = "no" ]; then + NEWEST_KERNEL= + fi + + echo $NEWEST_KERNEL +} + +NAME=$1 +VERSION=$2 +TARBALL_ROOT=$3 +ARCH=$4 +UPGRADE=$5 + +if [ -z "$NAME" ] || [ -z "$VERSION" ]; then + echo "Need NAME, and VERSION defined" + echo "ARCH is optional" + exit 1 +fi + +# read framework configuration options +if [ -r /etc/dkms/framework.conf ]; then + . /etc/dkms/framework.conf +fi + +KERNELS=$(ls /lib/modules/ 2>/dev/null || true) +CURRENT_KERNEL=$(uname -r) + +#We never want to keep an older version side by side to prevent conflicts +if [ -e "/var/lib/dkms/$NAME/$VERSION" ]; then + echo "Removing old $NAME-$VERSION DKMS files..." + dkms remove -m $NAME -v $VERSION --all +fi + +#Load new files, by source package and by tarball +if [ -f "$TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz" ]; then + if ! dkms ldtarball --archive "$TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz"; then + echo "" + echo "" + echo "Unable to load DKMS tarball $TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz." + echo "Common causes include: " + echo " - You must be using DKMS 2.1.0.0 or later to support binaries only" + echo " distribution specific archives." + echo " - Corrupt distribution specific archive" + echo "" + echo "" + exit 2 + fi +elif [ -d "/usr/src/$NAME-$VERSION" ]; then + echo "Loading new $NAME-$VERSION DKMS files..." + dkms add -m $NAME -v $VERSION > /dev/null +fi + +# On 1st installation, let us look for a directory +# in /lib/modules which matches `uname -r`. If none +# is found it is possible that buildd is being used +# and that uname -r is giving us the name of the +# kernel used by the buildd machine. +# +# If this is the case we try to build the kernel +# module for each kernel which has a directory in +# /lib/modules. Furthermore we will have to tell +# DKMS which architecture it should build the module +# for (e.g. if the buildd machine is using a +# 2.6.24-23-xen 64bit kernel). +# +# NOTE: if the headers are not installed then the +# module won't be built, as usual + +# Here we look for the most recent kernel so that we can +# build the module for it (in addition to doing it for the +# current kernel. +NEWEST_KERNEL=$(get_newest_kernel) + +if [ -z "$autoinstall_all_kernels" ]; then + # If the current kernel is installed on the system or chroot + if [ `_is_kernel_name_correct $CURRENT_KERNEL` = "yes" ]; then + if [ -n "$NEWEST_KERNEL" ] && [ ${CURRENT_KERNEL} != ${NEWEST_KERNEL} ]; then + KERNELS="$CURRENT_KERNEL $NEWEST_KERNEL" + else + KERNELS=$CURRENT_KERNEL + fi + # The current kernel is not useful as it's not installed + else + echo "It is likely that $CURRENT_KERNEL belongs to a chroot's host" + + # Let's use only the newest kernel if this is not a first installation + # otherwise build for all kernels + if [ -n "$NEWEST_KERNEL" -a -n "$UPGRADE" ]; then + KERNELS="$NEWEST_KERNEL" + fi + fi +fi + +# Take care of displaying newline separated list +echo "Building for $KERNELS" | tr '\n' ',' \ + | sed -e 's/,/, /g; s/, $/\n/; s/, \([^,]\+\)$/ and \1/' + +if [ -n "$ARCH" ]; then + if which lsb_release >/dev/null && [ $(lsb_release -s -i) = "Ubuntu" ]; then + case $ARCH in + amd64) + ARCH="x86_64" + ;; + lpia|i?86) + ARCH="i686" + ;; + esac + fi + echo "Building for architecture $ARCH" + ARCH="-a $ARCH" +fi + +for KERNEL in $KERNELS; do + dkms_status=`dkms status -m $NAME -v $VERSION -k $KERNEL $ARCH` + if [ `echo $KERNEL | grep -c "BOOT"` -gt 0 ]; then + echo "" + echo "Module build and install for $KERNEL was skipped as " + echo "it is a BOOT variant" + continue + fi + + + #if the module isn't yet built, try to build it + if [ `echo $dkms_status | grep -c ": built"` -eq 0 ]; then + if [ ! -L /var/lib/dkms/$NAME/$VERSION/source ]; then + echo "This package appears to be a binaries-only package" + echo " you will not be able to build against kernel $KERNEL" + echo " since the package source was not provided" + continue + fi + if _check_kernel_dir $KERNEL; then + echo "Building initial module for $KERNEL" + set +e + dkms build -m $NAME -v $VERSION -k $KERNEL $ARCH > /dev/null + case $? in + 9) + set -e + echo "Skipped." + continue + ;; + 0) + set -e + echo "Done." + ;; + *) + exit $? + ;; + esac + dkms_status=`dkms status -m $NAME -v $VERSION -k $KERNEL $ARCH` + else + echo "Module build for kernel $KERNEL was skipped since the" + echo "kernel headers for this kernel does not seem to be installed." + fi + fi + + #if the module is built (either pre-built or just now), install it + if [ `echo $dkms_status | grep -c ": built"` -eq 1 ] && + [ `echo $dkms_status | grep -c ": installed"` -eq 0 ]; then + dkms install -m $NAME -v $VERSION -k $KERNEL $ARCH + fi +done + diff -Nru slimbook-qc71-0.1/debian/changelog slimbook-qc71-0.2/debian/changelog --- slimbook-qc71-0.1/debian/changelog 2022-08-17 13:09:27.000000000 +0000 +++ slimbook-qc71-0.2/debian/changelog 2022-08-18 13:19:13.000000000 +0000 @@ -1,11 +1,12 @@ -slimbook-qc71 (0.1) focal; urgency=low +slimbook-qc71 (0.2) focal; urgency=low + + * Initial release - * Initial release. + -- Slimbook Thu, 18 Aug 2022 15:19:13 +0200 - -- Slimbook Wed, 17 Aug 2022 16:18:43 +0200 +slimbook-qc71 (0.1) focal; urgency=low -slimbook-qc71 (0.0) focal; urgency=low + * Automatically packaged by DKMS. - * Initial release. + -- Slimbook Thu, 18 Aug 2022 15:19:13 +0200 - -- Slimbook Wed, 17 Aug 2022 12:18:43 +0200 diff -Nru slimbook-qc71-0.1/debian/control slimbook-qc71-0.2/debian/control --- slimbook-qc71-0.1/debian/control 2022-08-17 12:57:07.000000000 +0000 +++ slimbook-qc71-0.2/debian/control 2022-08-18 13:19:13.000000000 +0000 @@ -2,12 +2,11 @@ Section: misc Priority: optional Maintainer: Slimbook -Build-Depends: debhelper (>= 7), dkms +Build-Depends: debhelper (>= 9), dkms Standards-Version: 3.8.1 Package: slimbook-qc71 Architecture: amd64 -Provides: slimbook-qc71-modules (= 0.0) +Provides: qc71-laptop-modules (= 0.1) Depends: dkms (>= 1.95), ${misc:Depends} -Description: slimbook-qc71 driver in DKMS format. - Slimbook extension of pobrn/qc71_laptop +Description: qc71-laptop driver in DKMS format. diff -Nru slimbook-qc71-0.1/debian/copyright slimbook-qc71-0.2/debian/copyright --- slimbook-qc71-0.1/debian/copyright 2022-08-17 13:00:02.000000000 +0000 +++ slimbook-qc71-0.2/debian/copyright 2022-08-18 13:19:13.000000000 +0000 @@ -1,22 +1,2 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: slimbook-qc71 -Source: https://github.com/slimbook -Files: * -Copyright: 2022 Slimbook -License: GPL-2 - -License: GPL-2 - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +This copyright has not been completed by the author of this package. diff -Nru slimbook-qc71-0.1/debian/postinst slimbook-qc71-0.2/debian/postinst --- slimbook-qc71-0.1/debian/postinst 2022-08-17 13:01:25.000000000 +0000 +++ slimbook-qc71-0.2/debian/postinst 2022-08-18 13:19:13.000000000 +0000 @@ -6,8 +6,8 @@ set -e -NAME=slimbook-qc71 -PACKAGE_NAME=$NAME +NAME=qc71_laptop +PACKAGE_NAME=$NAME-dkms DEB_NAME=$(echo $PACKAGE_NAME | sed 's,_,-,') CVERSION=`dpkg-query -W -f='${Version}' $DEB_NAME | awk -F "-" '{print $1}' | cut -d\: -f2` ARCH=`dpkg-architecture -qDEB_BUILD_GNU_CPU` diff -Nru slimbook-qc71-0.1/debian/prerm slimbook-qc71-0.2/debian/prerm --- slimbook-qc71-0.1/debian/prerm 2022-08-17 13:01:25.000000000 +0000 +++ slimbook-qc71-0.2/debian/prerm 2022-08-18 13:19:13.000000000 +0000 @@ -1,7 +1,7 @@ #!/bin/sh -NAME=slimbook-qc71 -VERSION=0.0 +NAME=qc71_laptop +VERSION=0.1 set -e diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/share/qc71_laptop-dkms/postinst slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/share/qc71_laptop-dkms/postinst --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/share/qc71_laptop-dkms/postinst 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/share/qc71_laptop-dkms/postinst 2022-08-18 13:19:13.000000000 +0000 @@ -0,0 +1,294 @@ +#!/bin/sh +# Copyright (C) 2002-2005 Flavio Stanchina +# Copyright (C) 2005-2006 Aric Cyr +# Copyright (C) 2007 Mario Limonciello +# Copyright (C) 2009 Alberto Milone + +set -e + +. /usr/share/debconf/confmodule + +uname_s=$(uname -s) + +_get_kernel_dir() { + KVER=$1 + case ${uname_s} in + Linux) DIR="/lib/modules/$KVER/build" ;; + GNU/kFreeBSD) DIR="/usr/src/kfreebsd-headers-$KVER/sys" ;; + esac + echo $DIR +} + +_check_kernel_dir() { + DIR=$(_get_kernel_dir $1) + case ${uname_s} in + Linux) test -e $DIR/include ;; + GNU/kFreeBSD) test -e $DIR/kern && test -e $DIR/conf/kmod.mk ;; + *) return 1 ;; + esac + return $? +} + +# Check the existence of a kernel named as $1 +_is_kernel_name_correct() { + CORRECT="no" + KERNEL_NAME=$1 + + for kernel in /boot/config-*; do + [ -f "$kernel" ] || continue + KERNEL=${kernel#*-} + if [ "${KERNEL}" = "${KERNEL_NAME}" ]; then + CORRECT="yes" + break + fi + done + + echo $CORRECT +} + + +# Get the most recent kernel on Debian based systems. This keeps +# into account both the version and the ABI. If the current kernel +# is the most recent kernel then the function will print a null string. +_get_newest_kernel_debian() { + NEWEST_KERNEL= + NEWEST_VERSION= + NEWEST_ABI= + + for kernel in /boot/config-*; do + [ -f "$kernel" ] || continue + KERNEL=${kernel#*-} + KERNEL_VERSION=${KERNEL%%-*} + ABI=${KERNEL#*-} + ABI=${ABI%%-*} + + if [ -z "$NEWEST_KERNEL" ]; then + # The 1st time get a version which is bigger than $1 + COMPARE_TO=$1 + else + # Get the biggest version + COMPARE_TO="$NEWEST_VERSION-$NEWEST_ABI" + fi + + # if $kernel is greater than $COMPARE_TO + if [ `dpkg --compare-versions "$KERNEL_VERSION-$ABI" ge "$COMPARE_TO" && echo "yes" || \ + echo "no"` = "yes" ]; then + NEWEST_KERNEL=$KERNEL + NEWEST_VERSION=$KERNEL_VERSION + NEWEST_ABI=$ABI + fi + done + + echo "$NEWEST_KERNEL" +} + +# Get the most recent kernel in Rhel based systems. If the current kernel +# is the most recent kernel then the function will print a null string. +_get_newest_kernel_rhel() { + NEWEST_KERNEL= + + LAST_INSTALLED_KERNEL=$(rpm -q --whatprovides kernel --last | grep kernel -m1 | cut -f1 -d' ') + + LIK_FORMATTED_NAME=$(rpm -q $LAST_INSTALLED_KERNEL --queryformat="%{VERSION}-%{RELEASE}.%{ARCH}\n") + + if [ `echo $LIK_FORMATTED_NAME | grep 2.6 >/dev/null` ]; then + # Fedora and Suse + NEWEST_KERNEL=$LIK_FORMATTED_NAME + else + # Hack for Mandriva where $LIK_FORMATTED_NAME is broken + LIK_NAME=$(rpm -q $LAST_INSTALLED_KERNEL --queryformat="%{NAME}\n") + LIK_TYPE=${LIK_NAME#kernel-} + LIK_TYPE=${LIK_TYPE%%-*} + LIK_STRIPPED=${LIK_NAME#kernel-} + LIK_STRIPPED=${LIK_STRIPPED#$LIK_TYPE-} + LIK_STRIPPED_BASE=${LIK_STRIPPED%%-*} + LIK_STRIPPED_END=${LIK_STRIPPED#$LIK_STRIPPED_BASE-} + LIK_FINAL=$LIK_STRIPPED_BASE-$LIK_TYPE-$LIK_STRIPPED_END + + NEWEST_KERNEL=$LIK_FINAL + fi + + echo $NEWEST_KERNEL +} + +# Get the newest kernel on Debian and Rhel based systems. +get_newest_kernel() { + NEWEST_KERNEL= + # Try Debian first as rpm can be installed in Debian based distros + if [ -e /usr/bin/dpkg ]; then + # If DEB based + CURRENT_VERSION=${CURRENT_KERNEL%%-*} + CURRENT_ABI=${CURRENT_KERNEL#*-} + CURRENT_FLAVOUR=${CURRENT_ABI#*-} + CURRENT_ABI=${CURRENT_ABI%%-*} + NEWEST_KERNEL=$(_get_newest_kernel_debian "$CURRENT_VERSION-$CURRENT_ABI") + + elif [ `which rpm >/dev/null` ]; then + # If RPM based + NEWEST_KERNEL=$(_get_newest_kernel_rhel) + fi + + # Make sure that kernel name that we extracted corresponds to an installed + # kernel + if [ -n "$NEWEST_KERNEL" ] && [ `_is_kernel_name_correct $NEWEST_KERNEL` = "no" ]; then + NEWEST_KERNEL= + fi + + echo $NEWEST_KERNEL +} + +NAME=$1 +VERSION=$2 +TARBALL_ROOT=$3 +ARCH=$4 +UPGRADE=$5 + +if [ -z "$NAME" ] || [ -z "$VERSION" ]; then + echo "Need NAME, and VERSION defined" + echo "ARCH is optional" + exit 1 +fi + +# read framework configuration options +if [ -r /etc/dkms/framework.conf ]; then + . /etc/dkms/framework.conf +fi + +KERNELS=$(ls /lib/modules/ 2>/dev/null || true) +CURRENT_KERNEL=$(uname -r) + +#We never want to keep an older version side by side to prevent conflicts +if [ -e "/var/lib/dkms/$NAME/$VERSION" ]; then + echo "Removing old $NAME-$VERSION DKMS files..." + dkms remove -m $NAME -v $VERSION --all +fi + +#Load new files, by source package and by tarball +if [ -f "$TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz" ]; then + if ! dkms ldtarball --archive "$TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz"; then + echo "" + echo "" + echo "Unable to load DKMS tarball $TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz." + echo "Common causes include: " + echo " - You must be using DKMS 2.1.0.0 or later to support binaries only" + echo " distribution specific archives." + echo " - Corrupt distribution specific archive" + echo "" + echo "" + exit 2 + fi +elif [ -d "/usr/src/$NAME-$VERSION" ]; then + echo "Loading new $NAME-$VERSION DKMS files..." + dkms add -m $NAME -v $VERSION > /dev/null +fi + +# On 1st installation, let us look for a directory +# in /lib/modules which matches `uname -r`. If none +# is found it is possible that buildd is being used +# and that uname -r is giving us the name of the +# kernel used by the buildd machine. +# +# If this is the case we try to build the kernel +# module for each kernel which has a directory in +# /lib/modules. Furthermore we will have to tell +# DKMS which architecture it should build the module +# for (e.g. if the buildd machine is using a +# 2.6.24-23-xen 64bit kernel). +# +# NOTE: if the headers are not installed then the +# module won't be built, as usual + +# Here we look for the most recent kernel so that we can +# build the module for it (in addition to doing it for the +# current kernel. +NEWEST_KERNEL=$(get_newest_kernel) + +if [ -z "$autoinstall_all_kernels" ]; then + # If the current kernel is installed on the system or chroot + if [ `_is_kernel_name_correct $CURRENT_KERNEL` = "yes" ]; then + if [ -n "$NEWEST_KERNEL" ] && [ ${CURRENT_KERNEL} != ${NEWEST_KERNEL} ]; then + KERNELS="$CURRENT_KERNEL $NEWEST_KERNEL" + else + KERNELS=$CURRENT_KERNEL + fi + # The current kernel is not useful as it's not installed + else + echo "It is likely that $CURRENT_KERNEL belongs to a chroot's host" + + # Let's use only the newest kernel if this is not a first installation + # otherwise build for all kernels + if [ -n "$NEWEST_KERNEL" -a -n "$UPGRADE" ]; then + KERNELS="$NEWEST_KERNEL" + fi + fi +fi + +# Take care of displaying newline separated list +echo "Building for $KERNELS" | tr '\n' ',' \ + | sed -e 's/,/, /g; s/, $/\n/; s/, \([^,]\+\)$/ and \1/' + +if [ -n "$ARCH" ]; then + if which lsb_release >/dev/null && [ $(lsb_release -s -i) = "Ubuntu" ]; then + case $ARCH in + amd64) + ARCH="x86_64" + ;; + lpia|i?86) + ARCH="i686" + ;; + esac + fi + echo "Building for architecture $ARCH" + ARCH="-a $ARCH" +fi + +for KERNEL in $KERNELS; do + dkms_status=`dkms status -m $NAME -v $VERSION -k $KERNEL $ARCH` + if [ `echo $KERNEL | grep -c "BOOT"` -gt 0 ]; then + echo "" + echo "Module build and install for $KERNEL was skipped as " + echo "it is a BOOT variant" + continue + fi + + + #if the module isn't yet built, try to build it + if [ `echo $dkms_status | grep -c ": built"` -eq 0 ]; then + if [ ! -L /var/lib/dkms/$NAME/$VERSION/source ]; then + echo "This package appears to be a binaries-only package" + echo " you will not be able to build against kernel $KERNEL" + echo " since the package source was not provided" + continue + fi + if _check_kernel_dir $KERNEL; then + echo "Building initial module for $KERNEL" + set +e + dkms build -m $NAME -v $VERSION -k $KERNEL $ARCH > /dev/null + case $? in + 9) + set -e + echo "Skipped." + continue + ;; + 0) + set -e + echo "Done." + ;; + *) + exit $? + ;; + esac + dkms_status=`dkms status -m $NAME -v $VERSION -k $KERNEL $ARCH` + else + echo "Module build for kernel $KERNEL was skipped since the" + echo "kernel headers for this kernel does not seem to be installed." + fi + fi + + #if the module is built (either pre-built or just now), install it + if [ `echo $dkms_status | grep -c ": built"` -eq 1 ] && + [ `echo $dkms_status | grep -c ": installed"` -eq 0 ]; then + dkms install -m $NAME -v $VERSION -k $KERNEL $ARCH + fi +done + diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/battery.c slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/battery.c --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/battery.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/battery.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "ec.h" +#include "features.h" + +/* ========================================================================== */ + +#if IS_ENABLED(CONFIG_ACPI_BATTERY) + +static bool battery_hook_registered; + +static bool nobattery; +module_param(nobattery, bool, 0444); +MODULE_PARM_DESC(nobattery, "do not expose battery related controls (default=false)"); + +/* ========================================================================== */ + +static ssize_t charge_control_end_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = ec_read_byte(BATT_CHARGE_CTRL_ADDR); + + if (status < 0) + return status; + + status &= BATT_CHARGE_CTRL_VALUE_MASK; + + if (status == 0) + status = 100; + + return sprintf(buf, "%d\n", status); +} + +static ssize_t charge_control_end_threshold_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status, value; + + if (kstrtoint(buf, 10, &value) || !(1 <= value && value <= 100)) + return -EINVAL; + + status = ec_read_byte(BATT_CHARGE_CTRL_ADDR); + if (status < 0) + return status; + + if (value == 100) + value = 0; + + status = (status & ~BATT_CHARGE_CTRL_VALUE_MASK) | value; + + status = ec_write_byte(BATT_CHARGE_CTRL_ADDR, status); + + if (status < 0) + return status; + + return count; +} + +static DEVICE_ATTR_RW(charge_control_end_threshold); +static struct attribute *qc71_laptop_batt_attrs[] = { + &dev_attr_charge_control_end_threshold.attr, + NULL +}; +ATTRIBUTE_GROUPS(qc71_laptop_batt); + +static int qc71_laptop_batt_add(struct power_supply *battery) +{ + if (strcmp(battery->desc->name, "BAT0") != 0) + return 0; + + return device_add_groups(&battery->dev, qc71_laptop_batt_groups); +} + +static int qc71_laptop_batt_remove(struct power_supply *battery) +{ + if (strcmp(battery->desc->name, "BAT0") != 0) + return 0; + + device_remove_groups(&battery->dev, qc71_laptop_batt_groups); + return 0; +} + +static struct acpi_battery_hook qc71_laptop_batt_hook = { + .add_battery = qc71_laptop_batt_add, + .remove_battery = qc71_laptop_batt_remove, + .name = "QC71 laptop battery extension", +}; + +int __init qc71_battery_setup(void) +{ + if (nobattery || !qc71_features.batt_charge_limit) + return -ENODEV; + + battery_hook_register(&qc71_laptop_batt_hook); + battery_hook_registered = true; + + return 0; +} + +void qc71_battery_cleanup(void) +{ + if (battery_hook_registered) + battery_hook_unregister(&qc71_laptop_batt_hook); +} + +#endif diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/battery.h slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/battery.h --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/battery.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/battery.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_BATTERY_H +#define QC71_BATTERY_H + +#if IS_ENABLED(CONFIG_ACPI_BATTERY) + +#include + +int __init qc71_battery_setup(void); +void qc71_battery_cleanup(void); + +#else + +static inline int qc71_battery_setup(void) +{ + return 0; +} + +static inline void qc71_battery_cleanup(void) +{ + +} + +#endif + +#endif /* QC71_BATTERY_H */ diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/debugfs.c slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/debugfs.c --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/debugfs.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/debugfs.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include +#include +#include + +#include "debugfs.h" +#include "ec.h" + +#if IS_ENABLED(CONFIG_DEBUG_FS) + +#define DEBUGFS_DIR_NAME KBUILD_MODNAME + +static const struct qc71_debugfs_attr { + const char *name; + uint16_t addr; +} qc71_debugfs_attrs[] = { + {"1108", 1108}, + + {"ap_bios_byte", AP_BIOS_BYTE_ADDR}, + + {"batt_alert", BATT_ALERT_ADDR}, + {"batt_charge_ctrl", BATT_CHARGE_CTRL_ADDR}, + {"batt_status", BATT_STATUS_ADDR}, + {"battt_temp", BATT_TEMP_ADDR}, + {"bios_ctrl_1", BIOS_CTRL_1_ADDR}, + {"bios_ctrl_2", BIOS_CTRL_2_ADDR}, + {"bios_ctrl_3", BIOS_CTRL_3_ADDR}, + {"bios_info_1", BIOS_INFO_1_ADDR}, + {"bios_info_5", BIOS_INFO_5_ADDR}, + + {"ctrl_1", CTRL_1_ADDR}, + {"ctrl_2", CTRL_2_ADDR}, + {"ctrl_3", CTRL_3_ADDR}, + {"ctrl_4", CTRL_4_ADDR}, + {"ctrl_5", CTRL_5_ADDR}, + {"ctrl_6", CTRL_6_ADDR}, + + {"device_status", DEVICE_STATUS_ADDR}, + + {"fan_ctrl", FAN_CTRL_ADDR}, + {"fan_mode_index", FAN_MODE_INDEX_ADDR}, + {"fan_temp_1", FAN_TEMP_1_ADDR}, + {"fan_temp_2", FAN_TEMP_2_ADDR}, + {"fan_pwm_1", FAN_PWM_1_ADDR}, + {"fan_pwm_2", FAN_PWM_2_ADDR}, + + /* setting these don't seem to work */ + {"fan_l1_pwm", ADDR(0x07, 0x43)}, + {"fan_l2_pwm", ADDR(0x07, 0x44)}, + {"fan_l3_pwm", ADDR(0x07, 0x45)}, + /* seemingly there is another level here, fan_ctrl=0x84, pwm=0x5a */ + {"fan_l4_pwm", ADDR(0x07, 0x46)}, + {"fan_l5_pwm", ADDR(0x07, 0x47)}, /* this is seemingly ignored, fan_ctrl=0x86, pwm=0xb4 */ + + {"fan_l1_pwm_default", ADDR(0x07, 0x86)}, + {"fan_l2_pwm_default", ADDR(0x07, 0x87)}, + {"fan_l3_pwm_default", ADDR(0x07, 0x88)}, + {"fan_l4_pwm_default", ADDR(0x07, 0x89)}, + {"fan_l5_pwm_default", ADDR(0x07, 0x8A)}, + + /* these don't seem to work */ + {"fan_min_speed", 1950}, + {"fan_min_temp", 1951}, + {"fan_extra_speed", 1952}, + + {"lightbar_ctrl", LIGHTBAR_CTRL_ADDR}, + {"lightbar_red", LIGHTBAR_RED_ADDR}, + {"lightbar_green", LIGHTBAR_GREEN_ADDR}, + {"lightbar_blue", LIGHTBAR_BLUE_ADDR}, + + {"keyboard_type", KEYBOARD_TYPE_ADDR}, + + {"support_1", SUPPORT_1_ADDR}, + {"support_2", SUPPORT_2_ADDR}, + {"support_5", SUPPORT_5_ADDR}, + {"status_1", STATUS_1_ADDR}, + + {"platform_id", PLATFORM_ID_ADDR}, + {"power_source", POWER_SOURCE_ADDR}, + {"project_id", PROJ_ID_ADDR}, + {"power_status", POWER_STATUS_ADDR}, + {"pl_1", PL1_ADDR}, + {"pl_2", PL2_ADDR}, + {"pl_4", PL4_ADDR}, + + {"trigger_1", TRIGGER_1_ADDR}, + {"trigger_2", TRIGGER_2_ADDR}, +}; + +/* ========================================================================== */ + +static bool debugregs; +module_param(debugregs, bool, 0444); +MODULE_PARM_DESC(debugregs, "expose various EC registers in debugfs (default=false)"); + +static struct dentry *qc71_debugfs_dir, + *qc71_debugfs_regs_dir; + +/* ========================================================================== */ + +static int get_debugfs_byte(void *data, u64 *value) +{ + const struct qc71_debugfs_attr *attr = data; + int status = ec_read_byte(attr->addr); + + if (status < 0) + return status; + + *value = status; + + return 0; +} + +static int set_debugfs_byte(void *data, u64 value) +{ + const struct qc71_debugfs_attr *attr = data; + int status; + + if (value > U8_MAX) + return -EINVAL; + + status = ec_write_byte(attr->addr, (uint8_t) value); + + if (status < 0) + return status; + + return status; +} + +DEFINE_DEBUGFS_ATTRIBUTE(qc71_debugfs_fops, get_debugfs_byte, set_debugfs_byte, "0x%02llx\n"); + +/* ========================================================================== */ + +static ssize_t qc71_debugfs_ec_read(struct file *f, char __user *buf, size_t count, loff_t *offset) +{ + size_t i; + + for (i = 0; *offset + i < U16_MAX && i < count; i++) { + int err = ec_read_byte(*offset + i); + u8 byte; + + if (signal_pending(current)) + return -EINTR; + + if (err < 0) { + if (i) + break; + + return err; + } + + byte = err; + + if (put_user(byte, buf + i)) + return -EFAULT; + } + + *offset += i; + + return i; +} + +static ssize_t qc71_debugfs_ec_write(struct file *f, const char __user *buf, size_t count, loff_t *offset) +{ + size_t i; + + for (i = 0; *offset + i < U16_MAX && i < count; i++) { + int err; + u8 byte; + + if (get_user(byte, buf + i)) + return -EFAULT; + + err = ec_write_byte(*offset + i, byte); + if (err) { + if (i) + break; + + return err; + } + + if (signal_pending(current)) + return -EINTR; + } + + *offset += i; + + return i; +} + +static const struct file_operations qc71_debugfs_ec_fops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = qc71_debugfs_ec_read, + .write = qc71_debugfs_ec_write, + .llseek = default_llseek, +}; + +/* ========================================================================== */ + +int __init qc71_debugfs_setup(void) +{ + struct dentry *d; + int err = 0; + size_t i; + + if (!debugregs) + return -ENODEV; + + qc71_debugfs_dir = debugfs_create_dir(DEBUGFS_DIR_NAME, NULL); + + if (IS_ERR(qc71_debugfs_dir)) { + err = PTR_ERR(qc71_debugfs_dir); + goto out; + } + + qc71_debugfs_regs_dir = debugfs_create_dir("regs", qc71_debugfs_dir); + + if (IS_ERR(qc71_debugfs_regs_dir)) { + err = PTR_ERR(qc71_debugfs_regs_dir); + debugfs_remove_recursive(qc71_debugfs_dir); + goto out; + } + + for (i = 0; i < ARRAY_SIZE(qc71_debugfs_attrs); i++) { + const struct qc71_debugfs_attr *attr = &qc71_debugfs_attrs[i]; + + d = debugfs_create_file(attr->name, 0600, qc71_debugfs_regs_dir, + (void *) attr, &qc71_debugfs_fops); + + if (IS_ERR(d)) { + err = PTR_ERR(d); + debugfs_remove_recursive(qc71_debugfs_dir); + goto out; + } + } + + d = debugfs_create_file("ec", 0600, qc71_debugfs_dir, NULL, &qc71_debugfs_ec_fops); + if (IS_ERR(d)) { + err = PTR_ERR(d); + debugfs_remove_recursive(qc71_debugfs_dir); + goto out; + } + +out: + return err; +} + +void qc71_debugfs_cleanup(void) +{ + /* checks if IS_ERR_OR_NULL() */ + debugfs_remove_recursive(qc71_debugfs_dir); +} + +#endif diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/debugfs.h slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/debugfs.h --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/debugfs.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/debugfs.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_DEBUGFS_H +#define QC71_DEBUGFS_H + +#if IS_ENABLED(CONFIG_DEBUG_FS) + +#include + +int __init qc71_debugfs_setup(void); +void qc71_debugfs_cleanup(void); + +#else + +static inline int qc71_debugfs_setup(void) +{ + return 0; +} + +static inline void qc71_debugfs_cleanup(void) +{ + +} + +#endif + +#endif /* QC71_DEBUGFS_H */ diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/dkms.conf slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/dkms.conf --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/dkms.conf 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/dkms.conf 2022-08-18 12:53:42.000000000 +0000 @@ -0,0 +1,7 @@ +MAKE="make KDIR=${kernel_source_dir}" +CLEAN="make KDIR=${kernel_source_dir} clean" +BUILT_MODULE_NAME=qc71_laptop +PACKAGE_NAME=qc71_laptop +PACKAGE_VERSION=0.1 +DEST_MODULE_LOCATION=/kernel/drivers/platform/x86 +AUTOINSTALL=yes diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/ec.c slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/ec.c --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/ec.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/ec.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include +#include +#include + +#include "ec.h" +#include "wmi.h" + +/* ========================================================================== */ + +static DECLARE_RWSEM(ec_lock); + +/* ========================================================================== */ + +int __must_check qc71_ec_lock(void) +{ + return down_write_killable(&ec_lock); +} + +void qc71_ec_unlock(void) +{ + up_write(&ec_lock); +} + +int __must_check qc71_ec_transaction(uint16_t addr, uint16_t data, + union qc71_ec_result *result, bool read) +{ + uint8_t buf[] = { + addr & 0xFF, + addr >> 8, + data & 0xFF, + data >> 8, + 0, + read ? 1 : 0, + 0, + 0, + }; + static_assert(ARRAY_SIZE(buf) == 8); + + /* the returned ACPI_TYPE_BUFFER is 40 bytes long for some reason ... */ + uint8_t output_buf[sizeof(union acpi_object) + 40]; + + struct acpi_buffer input = { sizeof(buf), buf }, + output = { sizeof(output_buf), output_buf }; + union acpi_object *obj; + acpi_status status = AE_OK; + int err; + + if (read) err = down_read_killable(&ec_lock); + else err = down_write_killable(&ec_lock); + + if (err) + goto out; + + memset(output_buf, 0, sizeof(output_buf)); + + status = wmi_evaluate_method(QC71_WMI_WMBC_GUID, 0, + QC71_WMBC_GETSETULONG_ID, &input, &output); + + if (read) up_read(&ec_lock); + else up_write(&ec_lock); + + if (ACPI_FAILURE(status)) { + err = -EIO; + goto out; + } + + obj = output.pointer; + + if (result) { + if (obj && obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= sizeof(*result)) { + memcpy(result, obj->buffer.pointer, sizeof(*result)); + } else { + err = -ENODATA; + goto out; + } + } + +out: + pr_debug( + "%s(addr=%#06x, data=%#06x, result=%c, read=%c)" + ": (%d) [%#010lx] %s" + ": [%*ph]\n", + + __func__, (unsigned int) addr, (unsigned int) data, + result ? 'y' : 'n', read ? 'y' : 'n', + err, (unsigned long) status, acpi_format_exception(status), + (obj && obj->type == ACPI_TYPE_BUFFER) ? + (int) min(sizeof(*result), (size_t) obj->buffer.length) : 0, + (obj && obj->type == ACPI_TYPE_BUFFER) ? + obj->buffer.pointer : NULL + ); + + return err; +} +ALLOW_ERROR_INJECTION(qc71_ec_transaction, ERRNO); diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/ec.h slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/ec.h --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/ec.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/ec.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_LAPTOP_EC_H +#define QC71_LAPTOP_EC_H + +#include +#include + +/* ========================================================================== */ +/* + * EC register addresses and bitmasks, + * some of them are not used, + * only for documentation + */ + +#define ADDR(page, offset) (((uint16_t)(page) << 8) | ((uint16_t)(offset))) + +/* ========================================================================== */ + +#define AP_BIOS_BYTE_ADDR ADDR(0x07, 0xA4) +#define AP_BIOS_BYTE_FN_LOCK_SWITCH BIT(3) + +/* ========================================================================== */ + +/* battery charger control register */ +#define BATT_CHARGE_CTRL_ADDR ADDR(0x07, 0xB9) +#define BATT_CHARGE_CTRL_VALUE_MASK GENMASK(6, 0) +#define BATT_CHARGE_CTRL_REACHED BIT(7) + +#define BATT_STATUS_ADDR ADDR(0x04, 0x32) +#define BATT_STATUS_DISCHARGING BIT(0) + +/* possibly temp (in C) = value / 10 + X */ +#define BATT_TEMP_ADDR ADDR(0x04, 0xA2) + +#define BATT_ALERT_ADDR ADDR(0x04, 0x94) + +#define BIOS_CTRL_1_ADDR ADDR(0x07, 0x4E) +#define BIOS_CTRL_1_FN_LOCK_STATUS BIT(4) + +#define BIOS_CTRL_2_ADDR ADDR(0x07, 0x82) +#define BIOS_CTRL_2_FAN_V2_NEW BIT(0) +#define BIOS_CTRL_2_FAN_QKEY BIT(1) +#define BIOS_CTRL_2_OFFICE_MODE_FAN_TABLE_TYPE BIT(2) +#define BIOS_CTRL_2_FAN_V3 BIT(3) +#define BIOS_CTRL_2_DEFAULT_MODE BIT(4) + +/* 3rd control register of a different kind */ +#define BIOS_CTRL_3_ADDR ADDR(0x7, 0xA3) +#define BIOS_CTRL_3_FAN_REDUCED_DUTY_CYCLE BIT(5) +#define BIOS_CTRL_3_FAN_ALWAYS_ON BIT(6) + +#define BIOS_INFO_1_ADDR ADDR(0x04, 0x9F) +#define BIOS_INFO_5_ADDR ADDR(0x04, 0x66) + +/* ========================================================================== */ + +#define CTRL_1_ADDR ADDR(0x07, 0x41) +#define CTRL_1_MANUAL_MODE BIT(0) +#define CTRL_1_ITE_KBD_EFFECT_REACTIVE BIT(3) +#define CTRL_1_FAN_ABNORMAL BIT(5) + +#define CTRL_2_ADDR ADDR(0x07, 0x8C) +#define CTRL_2_SINGLE_COLOR_KEYBOARD BIT(0) +#define CTRL_2_SINGLE_COLOR_KBD_BL_OFF BIT(1) +#define CTRL_2_TURBO_LEVEL_MASK GENMASK(3, 2) +#define CTRL_2_TURBO_LEVEL_0 0x00 +#define CTRL_2_TURBO_LEVEL_1 BIT(2) +#define CTRL_2_TURBO_LEVEL_2 BIT(3) +#define CTRL_2_TURBO_LEVEL_3 (BIT(2) | BIT(3)) +// #define CTRL_2_SINGLE_COLOR_KBD_? BIT(4) +#define CTRL_2_SINGLE_COLOR_KBD_BRIGHTNESS GENMASK(7, 5) + +#define CTRL_3_ADDR ADDR(0x07, 0xA5) +#define CTRL_3_PWR_LED_MASK GENMASK(1, 0) +#define CTRL_3_PWR_LED_NONE BIT(1) +#define CTRL_3_PWR_LED_BOTH BIT(0) +#define CTRL_3_PWR_LED_LEFT 0x00 +#define CTRL_3_FAN_QUIET BIT(2) +#define CTRL_3_OVERBOOST BIT(4) +#define CTRL_3_HIGH_PWR BIT(7) + +#define CTRL_4_ADDR ADDR(0x07, 0xA6) +#define CTRL_4_OVERBOOST_DYN_TEMP_OFF BIT(1) +#define CTRL_4_TOUCHPAD_TOGGLE_OFF BIT(6) + +#define CTRL_5_ADDR ADDR(0x07, 0xC5) + +#define CTRL_6_ADDR ADDR(0x07, 0x8E) + +/* ========================================================================== */ + +#define DEVICE_STATUS_ADDR ADDR(0x04, 0x7B) +#define DEVICE_STATUS_WIFI_ON BIT(7) +/* BIT(5) is seemingly also (un)set depending on the rfkill state (bluetooth?) */ + +/* ========================================================================== */ + +/* fan control register */ +#define FAN_CTRL_ADDR ADDR(0x07, 0x51) +#define FAN_CTRL_LEVEL_MASK GENMASK(2, 0) +#define FAN_CTRL_TURBO BIT(4) +#define FAN_CTRL_AUTO BIT(5) +#define FAN_CTRL_FAN_BOOST BIT(6) +#define FAN_CTRL_SILENT_MODE BIT(7) + +#define FAN_RPM_1_ADDR ADDR(0x04, 0x64) +#define FAN_RPM_2_ADDR ADDR(0x04, 0x6C) + +#define FAN_PWM_1_ADDR ADDR(0x18, 0x04) +#define FAN_PWM_2_ADDR ADDR(0x18, 0x09) + +#define FAN_TEMP_1_ADDR ADDR(0x04, 0x3e) +#define FAN_TEMP_2_ADDR ADDR(0x04, 0x4f) + +#define FAN_MODE_INDEX_ADDR ADDR(0x07, 0xAB) +#define FAN_MODE_INDEX_LOW_MASK GENMASK(3, 0) +#define FAN_MODE_INDEX_HIGH_MASK GENMASK(7, 4) + +/* ========================================================================== */ + +/* + * the actual keyboard type is seemingly determined from this number, + * the project id, the controller firmware version, + * and the HID usage page of the descriptor of the controller + */ +#define KEYBOARD_TYPE_ADDR ADDR(0x07, 0x3C) +#define KEYBOARD_TYPE_101 25 +#define KEYBOARD_TYPE_101M 41 +#define KEYBOARD_TYPE_102 17 +#define KEYBOARD_TYPE_102M 33 +#define KEYBOARD_TYPE_85 25 +#define KEYBOARD_TYPE_86 17 +#define KEYBOARD_TYPE_87 73 +#define KEYBOARD_TYPE_88 65 +#define KEYBOARD_TYPE_97 57 +#define KEYBOARD_TYPE_98 49 +#define KEYBOARD_TYPE_99 121 +#define KEYBOARD_TYPE_100 113 + +/* ========================================================================== */ + +/* lightbar control register */ +#define LIGHTBAR_CTRL_ADDR ADDR(0x07, 0x48) +#define LIGHTBAR_CTRL_POWER_SAVE BIT(1) +#define LIGHTBAR_CTRL_S0_OFF BIT(2) +#define LIGHTBAR_CTRL_S3_OFF BIT(3) +#define LIGHTBAR_CTRL_RAINBOW BIT(7) + +#define LIGHTBAR_RED_ADDR ADDR(0x07, 0x49) +#define LIGHTBAR_GREEN_ADDR ADDR(0x07, 0x4A) +#define LIGHTBAR_BLUE_ADDR ADDR(0x07, 0x4B) + +/* ========================================================================== */ + +#define PROJ_ID_ADDR ADDR(0x07, 0x40) +#define PROJ_ID_GIxKN 1 +#define PROJ_ID_GJxKN 2 +#define PROJ_ID_GKxCN 3 +#define PROJ_ID_GIxCN 4 +#define PROJ_ID_GJxCN 5 +#define PROJ_ID_GK5CN_X 6 +#define PROJ_ID_GK7CN_S 7 +#define PROJ_ID_GK7CPCS_GK5CQ7Z 8 +#define PROJ_ID_PF5NU1G_PF4LUXF 9 +#define PROJ_ID_IDP 11 +#define PROJ_ID_ID6Y 12 +#define PROJ_ID_ID7Y 13 +#define PROJ_ID_PF4MU_PF4MN_PF5MU 14 +#define PROJ_ID_CML_GAMING 15 +#define PROJ_ID_GK7NXXR 16 +#define PROJ_ID_GM5MU1Y 17 + +/* ========================================================================== */ + +#define STATUS_1_ADDR ADDR(0x07, 0x68) +#define STATUS_1_SUPER_KEY_LOCK BIT(0) +#define STATUS_1_LIGHTBAR BIT(1) +#define STATUS_1_FAN_BOOST BIT(2) + +#define SUPPORT_1_ADDR ADDR(0x07, 0x65) +#define SUPPORT_1_AIRPLANE_MODE BIT(0) +#define SUPPORT_1_GPS_SWITCH BIT(1) +#define SUPPORT_1_OVERCLOCK BIT(2) +#define SUPPORT_1_MACRO_KEY BIT(3) +#define SUPPORT_1_SHORTCUT_KEY BIT(4) +#define SUPPORT_1_SUPER_KEY_LOCK BIT(5) +#define SUPPORT_1_LIGHTBAR BIT(6) +#define SUPPORT_1_FAN_BOOST BIT(7) + +#define SUPPORT_2_ADDR ADDR(0x07, 0x66) +#define SUPPORT_2_SILENT_MODE BIT(0) +#define SUPPORT_2_USB_CHARGING BIT(1) +#define SUPPORT_2_SINGLE_ZONE_KBD BIT(2) +#define SUPPORT_2_CHINA_MODE BIT(5) +#define SUPPORT_2_MY_BATTERY BIT(6) + +#define SUPPORT_5_ADDR ADDR(0x07, 0x42) +#define SUPPORT_5_FAN_TURBO BIT(4) +#define SUPPORT_5_FAN BIT(5) + +#define SUPPORT_6 ADDR(0x07, 0x8E) +#define SUPPORT_6_FAN3P5 BIT(1) + +/* ========================================================================== */ + +#define TRIGGER_1_ADDR ADDR(0x07, 0x67) +#define TRIGGER_1_SUPER_KEY_LOCK BIT(0) +#define TRIGGER_1_LIGHTBAR BIT(1) +#define TRIGGER_1_FAN_BOOST BIT(2) +#define TRIGGER_1_SILENT_MODE BIT(3) +#define TRIGGER_1_USB_CHARGING BIT(4) + +#define TRIGGER_2_ADDR ADDR(0x07, 0x5D) + +/* ========================================================================== */ + +#define PLATFORM_ID_ADDR ADDR(0x04, 0x56) +#define POWER_STATUS_ADDR ADDR(0x04, 0x5E) +#define POWER_SOURCE_ADDR ADDR(0x04, 0x90) + +#define PL1_ADDR ADDR(0x07, 0x83) +#define PL2_ADDR ADDR(0x07, 0x84) +#define PL4_ADDR ADDR(0x07, 0x85) + +/* ========================================================================== */ + +union qc71_ec_result { + uint32_t dword; + struct { + uint16_t w1; + uint16_t w2; + } words; + struct { + uint8_t b1; + uint8_t b2; + uint8_t b3; + uint8_t b4; + } bytes; +}; + +int __must_check qc71_ec_transaction(uint16_t addr, uint16_t data, + union qc71_ec_result *result, bool read); + +static inline __must_check int qc71_ec_read(uint16_t addr, union qc71_ec_result *result) +{ + return qc71_ec_transaction(addr, 0, result, true); +} + +static inline __must_check int qc71_ec_write(uint16_t addr, uint16_t data) +{ + return qc71_ec_transaction(addr, data, NULL, false); +} + +static inline __must_check int ec_write_byte(uint16_t addr, uint8_t data) +{ + return qc71_ec_write(addr, data); +} + +static inline __must_check int ec_read_byte(uint16_t addr) +{ + union qc71_ec_result result; + int err; + + err = qc71_ec_read(addr, &result); + + if (err) + return err; + + return result.bytes.b1; +} + +#endif /* QC71_LAPTOP_EC_H */ diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/events.c slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/events.c --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/events.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/events.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "misc.h" +#include "pdev.h" +#include "wmi.h" + +/* ========================================================================== */ + +#define KBD_BL_LED_SUFFIX ":" LED_FUNCTION_KBD_BACKLIGHT + +/* ========================================================================== */ + +static struct { + const char *guid; + bool handler_installed; +} qc71_wmi_event_guids[] = { + { .guid = QC71_WMI_EVENT0_GUID }, + { .guid = QC71_WMI_EVENT1_GUID }, + { .guid = QC71_WMI_EVENT2_GUID }, +}; + +static const struct key_entry qc71_wmi_hotkeys[] = { + + /* reported via keyboard controller */ + { KE_IGNORE, 0x01, { KEY_CAPSLOCK }}, + { KE_IGNORE, 0x02, { KEY_NUMLOCK }}, + { KE_IGNORE, 0x03, { KEY_SCROLLLOCK }}, + + /* reported via "video bus" */ + { KE_IGNORE, 0x14, { KEY_BRIGHTNESSUP }}, + { KE_IGNORE, 0x15, { KEY_BRIGHTNESSDOWN }}, + + /* reported in automatic mode when rfkill state changes */ + { KE_SW, 0x1a, {.sw = { SW_RFKILL_ALL, 1 }}}, + { KE_SW, 0x1b, {.sw = { SW_RFKILL_ALL, 0 }}}, + + /* reported via keyboard controller */ + { KE_IGNORE, 0x35, { KEY_MUTE }}, + { KE_IGNORE, 0x36, { KEY_VOLUMEDOWN }}, + { KE_IGNORE, 0x37, { KEY_VOLUMEUP }}, + + /* + * not reported by other means when in manual mode, + * handled automatically when it automatic mode + */ + { KE_KEY, 0xa4, { KEY_RFKILL }}, + { KE_KEY, 0xb1, { KEY_KBDILLUMDOWN }}, + { KE_KEY, 0xb2, { KEY_KBDILLUMUP }}, + { KE_KEY, 0xb8, { KEY_FN_ESC }}, + + { KE_END } + +}; + +/* ========================================================================== */ + +static struct input_dev *qc71_input_dev; + +/* ========================================================================== */ + +static void toggle_fn_lock_from_event_handler(void) +{ + int status = qc71_fn_lock_get_state(); + + if (status < 0) + return; + + /* seemingly the returned status in the WMI event handler is not the current */ + pr_info("setting Fn lock state from %d to %d\n", !status, status); + qc71_fn_lock_set_state(status); +} + +#if IS_ENABLED(CONFIG_LEDS_BRIGHTNESS_HW_CHANGED) +extern struct rw_semaphore leds_list_lock; +extern struct list_head leds_list; + +static void emit_keyboard_led_hw_changed(void) +{ + struct led_classdev *led; + + if (down_read_killable(&leds_list_lock)) + return; + + list_for_each_entry (led, &leds_list, node) { + size_t name_length; + const char *suffix; + + if (!(led->flags & LED_BRIGHT_HW_CHANGED)) + continue; + + name_length = strlen(led->name); + + if (name_length < strlen(KBD_BL_LED_SUFFIX)) + continue; + + suffix = led->name + name_length - strlen(KBD_BL_LED_SUFFIX); + + if (strcmp(suffix, KBD_BL_LED_SUFFIX) == 0) { + if (mutex_lock_interruptible(&led->led_access)) + break; + + if (led_update_brightness(led) >= 0) + led_classdev_notify_brightness_hw_changed(led, led->brightness); + + mutex_unlock(&led->led_access); + break; + } + } + + up_read(&leds_list_lock); +} +#endif + +static void qc71_wmi_event_d2_handler(union acpi_object *obj) +{ + bool do_report = true; + + if (!obj || obj->type != ACPI_TYPE_INTEGER) + return; + + switch (obj->integer.value) { + /* caps lock */ + case 1: + pr_info("caps lock\n"); + break; + + /* num lock */ + case 2: + pr_info("num lock\n"); + break; + + /* scroll lock */ + case 3: + pr_info("scroll lock\n"); + break; + + /* touchpad on */ + case 4: + pr_info("touchpad on\n"); + break; + + /* touchpad off */ + case 5: + pr_info("touchpad off\n"); + break; + + /* increase screen brightness */ + case 20: + pr_info("increase screen brightness\n"); + /* do_report = !acpi_video_handles_brightness_key_presses() */ + break; + + /* decrease screen brightness */ + case 21: + pr_info("decrease screen brightness\n"); + /* do_report = !acpi_video_handles_brightness_key_presses() */ + break; + + /* radio on */ + case 26: + /* triggered in automatic mode when the rfkill hotkey is pressed */ + pr_info("radio on\n"); + break; + + /* radio off */ + case 27: + /* triggered in automatic mode when the rfkill hotkey is pressed */ + pr_info("radio off\n"); + break; + + /* mute/unmute */ + case 53: + pr_info("toggle mute\n"); + break; + + /* decrease volume */ + case 54: + pr_info("decrease volume\n"); + break; + + /* increase volume */ + case 55: + pr_info("increase volume\n"); + break; + + case 57: + pr_info("lightbar on\n"); + break; + + case 58: + pr_info("lightbar off\n"); + break; + + /* enable super key (win key) lock */ + case 64: + pr_info("enable super key lock\n"); + break; + + /* decrease volume */ + case 65: + pr_info("disable super key lock\n"); + break; + + /* enable/disable airplane mode */ + case 164: + pr_info("toggle airplane mode\n"); + break; + + /* super key (win key) lock state changed */ + case 165: + pr_info("super key lock state changed\n"); + sysfs_notify(&qc71_platform_dev->dev.kobj, NULL, "super_key_lock"); + break; + + case 166: + pr_info("lightbar state changed\n"); + break; + + /* fan boost state changed */ + case 167: + pr_info("fan boost state changed\n"); + break; + + /* charger unplugged/plugged in */ + case 171: + pr_info("AC plugged/unplugged\n"); + break; + + /* perf mode button pressed */ + case 176: + pr_info("change perf mode\n"); + /* TODO: should it be handled here? */ + break; + + /* increase keyboard backlight */ + case 177: + pr_info("keyboard backlight decrease\n"); + /* TODO: should it be handled here? */ + break; + + /* decrease keyboard backlight */ + case 178: + pr_info("keyboard backlight increase\n"); + /* TODO: should it be handled here? */ + break; + + /* toggle Fn lock (Fn+ESC)*/ + case 184: + pr_info("toggle Fn lock\n"); + toggle_fn_lock_from_event_handler(); + sysfs_notify(&qc71_platform_dev->dev.kobj, NULL, "fn_lock"); + break; + + /* keyboard backlight brightness changed */ + case 240: + pr_info("keyboard backlight changed\n"); + +#if IS_ENABLED(CONFIG_LEDS_BRIGHTNESS_HW_CHANGED) + emit_keyboard_led_hw_changed(); +#endif + break; + + default: + pr_warn("unknown code: %u\n", (unsigned int) obj->integer.value); + break; + } + + if (do_report && qc71_input_dev) + sparse_keymap_report_event(qc71_input_dev, + obj->integer.value, 1, true); + +} + +static void qc71_wmi_event_handler(u32 value, void *context) +{ + struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + + pr_info("%s(value=%#04x)\n", __func__, (unsigned int) value); + status = wmi_get_event_data(value, &response); + + if (ACPI_FAILURE(status)) { + pr_err("bad WMI event status: %#010x\n", (unsigned int) status); + return; + } + + obj = response.pointer; + + if (obj) { + pr_info("obj->type = %d\n", (int) obj->type); + if (obj->type == ACPI_TYPE_INTEGER) { + pr_info("int = %u\n", (unsigned int) obj->integer.value); + } else if (obj->type == ACPI_TYPE_STRING) { + pr_info("string = '%s'\n", obj->string.pointer); + } else if (obj->type == ACPI_TYPE_BUFFER) { + uint32_t i; + + for (i = 0; i < obj->buffer.length; i++) + pr_info("buf[%u] = %#04x\n", + (unsigned int) i, + (unsigned int) obj->buffer.pointer[i]); + } + } + + switch (value) { + case 0xd2: + qc71_wmi_event_d2_handler(obj); + break; + case 0xd1: + case 0xd0: + break; + } + + kfree(obj); +} + +static int __init setup_input_dev(void) +{ + int err = 0; + + qc71_input_dev = input_allocate_device(); + if (!qc71_input_dev) + return -ENOMEM; + + qc71_input_dev->name = "QC71 laptop input device"; + qc71_input_dev->phys = "qc71_laptop/input0"; + qc71_input_dev->id.bustype = BUS_HOST; + qc71_input_dev->dev.parent = &qc71_platform_dev->dev; + + err = sparse_keymap_setup(qc71_input_dev, qc71_wmi_hotkeys, NULL); + if (err) + goto err_free_device; + + err = qc71_rfkill_get_wifi_state(); + if (err >= 0) + input_report_switch(qc71_input_dev, SW_RFKILL_ALL, err); + else + input_report_switch(qc71_input_dev, SW_RFKILL_ALL, 1); + + err = input_register_device(qc71_input_dev); + if (err) + goto err_free_device; + + return err; + +err_free_device: + input_free_device(qc71_input_dev); + qc71_input_dev = NULL; + + return err; +} + +/* ========================================================================== */ + +int __init qc71_wmi_events_setup(void) +{ + int err = 0, i; + + (void) setup_input_dev(); + + for (i = 0; i < ARRAY_SIZE(qc71_wmi_event_guids); i++) { + const char *guid = qc71_wmi_event_guids[i].guid; + acpi_status status = + wmi_install_notify_handler(guid, qc71_wmi_event_handler, NULL); + + if (ACPI_FAILURE(status)) { + pr_warn("could not install WMI notify handler for '%s': [%#010lx] %s\n", + guid, (unsigned long) status, acpi_format_exception(status)); + } else { + qc71_wmi_event_guids[i].handler_installed = true; + } + } + + return err; +} + +void qc71_wmi_events_cleanup(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(qc71_wmi_event_guids); i++) { + if (qc71_wmi_event_guids[i].handler_installed) { + wmi_remove_notify_handler(qc71_wmi_event_guids[i].guid); + qc71_wmi_event_guids[i].handler_installed = false; + } + } + + if (qc71_input_dev) + input_unregister_device(qc71_input_dev); +} diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/events.h slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/events.h --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/events.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/events.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_WMI_EVENTS_H +#define QC71_WMI_EVENTS_H + +#if IS_ENABLED(CONFIG_LEDS_CLASS) + +#include + +int __init qc71_wmi_events_setup(void); +void qc71_wmi_events_cleanup(void); + +#else + +static inline int qc71_wmi_events_setup(void) +{ + return 0; +} + +static inline void qc71_wmi_events_cleanup(void) +{ + +} + +#endif + +#endif /* QC71_WMI_EVENTS_H */ diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/fan.c slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/fan.c --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/fan.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/fan.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0) +static inline int fixp_linear_interpolate(int x0, int y0, int x1, int y1, int x) +{ + if (y0 == y1 || x == x0) + return y0; + if (x1 == x0 || x == x1) + return y1; + + return y0 + ((y1 - y0) * (x - x0) / (x1 - x0)); +} +#else +#include /* fixp-arith.h needs it, but doesn't include it */ +#include +#endif + +#include +#include +#include + +#include "ec.h" +#include "fan.h" +#include "util.h" + +/* ========================================================================== */ + +static const uint16_t qc71_fan_rpm_addrs[] = { + FAN_RPM_1_ADDR, + FAN_RPM_2_ADDR, +}; + +static const uint16_t qc71_fan_pwm_addrs[] = { + FAN_PWM_1_ADDR, + FAN_PWM_2_ADDR, +}; + +static const uint16_t qc71_fan_temp_addrs[] = { + FAN_TEMP_1_ADDR, + FAN_TEMP_2_ADDR, +}; + +/* ========================================================================== */ + +static DEFINE_MUTEX(fan_lock); + +/* ========================================================================== */ + +static int qc71_fan_get_status(void) +{ + return ec_read_byte(FAN_CTRL_ADDR); +} + +/* 'fan_lock' must be held */ +static int qc71_fan_get_mode_unlocked(void) +{ + int err; + + lockdep_assert_held(&fan_lock); + + err = ec_read_byte(CTRL_1_ADDR); + if (err < 0) + return err; + + if (err & CTRL_1_MANUAL_MODE) { + err = qc71_fan_get_status(); + if (err < 0) + return err; + + if (err & FAN_CTRL_FAN_BOOST) { + err = qc71_fan_get_pwm(0); + + if (err < 0) + return err; + + if (err == FAN_MAX_PWM) + err = 0; /* disengaged */ + else + err = 1; /* manual */ + + } else if (err & FAN_CTRL_AUTO) { + err = 2; /* automatic fan control */ + } else { + err = 1; /* manual */ + } + } else { + err = 2; /* automatic fan control */ + } + + return err; +} + +/* ========================================================================== */ + +int qc71_fan_get_rpm(uint8_t fan_index) +{ + union qc71_ec_result res; + int err; + + if (fan_index >= ARRAY_SIZE(qc71_fan_rpm_addrs)) + return -EINVAL; + + err = qc71_ec_read(qc71_fan_rpm_addrs[fan_index], &res); + + if (err) + return err; + + return res.bytes.b1 << 8 | res.bytes.b2; +} + +int qc71_fan_query_abnorm(void) +{ + int res = ec_read_byte(CTRL_1_ADDR); + + if (res < 0) + return res; + + return !!(res & CTRL_1_FAN_ABNORMAL); +} + +int qc71_fan_get_pwm(uint8_t fan_index) +{ + int err; + + if (fan_index >= ARRAY_SIZE(qc71_fan_pwm_addrs)) + return -EINVAL; + + err = ec_read_byte(qc71_fan_pwm_addrs[fan_index]); + if (err < 0) + return err; + + return fixp_linear_interpolate(0, 0, FAN_MAX_PWM, U8_MAX, err); +} + +int qc71_fan_set_pwm(uint8_t fan_index, uint8_t pwm) +{ + if (fan_index >= ARRAY_SIZE(qc71_fan_pwm_addrs)) + return -EINVAL; + + return ec_write_byte(qc71_fan_pwm_addrs[fan_index], + fixp_linear_interpolate(0, 0, + U8_MAX, FAN_MAX_PWM, + pwm)); +} + +int qc71_fan_get_temp(uint8_t fan_index) +{ + if (fan_index >= ARRAY_SIZE(qc71_fan_temp_addrs)) + return -EINVAL; + + return ec_read_byte(qc71_fan_temp_addrs[fan_index]); +} + +int qc71_fan_get_mode(void) +{ + int err = mutex_lock_interruptible(&fan_lock); + + if (err) + return err; + + err = qc71_fan_get_mode_unlocked(); + + mutex_unlock(&fan_lock); + return err; +} + +int qc71_fan_set_mode(uint8_t mode) +{ + int err, oldpwm; + + err = mutex_lock_interruptible(&fan_lock); + if (err) + return err; + + switch (mode) { + case 0: + err = ec_write_byte(FAN_CTRL_ADDR, FAN_CTRL_FAN_BOOST); + if (err) + goto out; + + err = qc71_fan_set_pwm(0, FAN_MAX_PWM); + break; + case 1: + oldpwm = err = qc71_fan_get_pwm(0); + if (err < 0) + goto out; + + err = ec_write_byte(FAN_CTRL_ADDR, FAN_CTRL_FAN_BOOST); + if (err < 0) + goto out; + + err = qc71_fan_set_pwm(0, oldpwm); + if (err < 0) + (void) ec_write_byte(FAN_CTRL_ADDR, 0x80 | FAN_CTRL_AUTO); + /* try to restore automatic fan control */ + + break; + case 2: + err = ec_write_byte(FAN_CTRL_ADDR, 0x80 | FAN_CTRL_AUTO); + break; + default: + err = -EINVAL; + break; + } + +out: + mutex_unlock(&fan_lock); + return err; +} diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/fan.h slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/fan.h --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/fan.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/fan.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,22 @@ +#ifndef QC71_LAPTOP_FAN_H +#define QC71_LAPTOP_FAN_H + +#include + +/* ========================================================================== */ + +#define FAN_MAX_PWM 200 +#define FAN_CTRL_MAX_LEVEL 7 +#define FAN_CTRL_LEVEL(level) (128 + (level)) + +/* ========================================================================== */ + +int qc71_fan_get_rpm(uint8_t fan_index); +int qc71_fan_query_abnorm(void); +int qc71_fan_get_pwm(uint8_t fan_index); +int qc71_fan_set_pwm(uint8_t fan_index, uint8_t pwm); +int qc71_fan_get_temp(uint8_t fan_index); +int qc71_fan_get_mode(void); +int qc71_fan_set_mode(uint8_t mode); + +#endif /* QC71_LAPTOP_FAN_H */ diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/features.c slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/features.c --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/features.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/features.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include +#include +#include + +#include "ec.h" +#include "features.h" + +/* ========================================================================== */ + +struct oem_string_walker_data { + char *value; + int index; +}; + +/* ========================================================================== */ + +static int __init slimbook_dmi_cb(const struct dmi_system_id *id) +{ + qc71_features.fn_lock = true; + qc71_features.silent_mode = true; + + return 1; +} + +static const struct dmi_system_id qc71_dmi_table[] __initconst = { + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "LAPQC71"), + { } + } + }, + { + /* https://avell.com.br/avell-a60-muv-295765 */ + .matches = { + DMI_EXACT_MATCH(DMI_CHASSIS_VENDOR, "Avell High Performance"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "A60 MUV"), + { } + } + }, + { + /* Slimbook PROX AMD */ + .callback = slimbook_dmi_cb, + .matches = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "PROX-AMD"), + { } + } + }, + { + /* Slimbook PROX15 AMD */ + .callback = slimbook_dmi_cb, + .matches = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "PROX15-AMD"), + { } + } + }, + { + /* Slimbook PROX AMD5 */ + .callback = slimbook_dmi_cb, + .matches = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME,"PROX-AMD5"), + { } + } + }, + { + /* Slimbook PROX15 AMD5 */ + .callback = slimbook_dmi_cb, + .matches = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME,"PROX15-AMD5"), + { } + } + }, + { } +}; + +/* ========================================================================== */ + +struct qc71_features_struct qc71_features; + +/* ========================================================================== */ + +static void __init oem_string_walker(const struct dmi_header *dm, void *ptr) +{ + int i, count; + const uint8_t *s; + struct oem_string_walker_data *data = ptr; + + if (dm->type != 11 || dm->length < 5 || !IS_ERR_OR_NULL(data->value)) + return; + + count = *(uint8_t *)(dm + 1); + + if (data->index >= count) + return; + + i = 0; + s = ((uint8_t *)dm) + dm->length; + + while (i++ < data->index && *s) + s += strlen(s) + 1; + + data->value = kstrdup(s, GFP_KERNEL); + + if (!data->value) + data->value = ERR_PTR(-ENOMEM); +} + +static char * __init read_oem_string(int index) +{ + struct oem_string_walker_data d = {.value = ERR_PTR(-ENOENT), + .index = index}; + int err = dmi_walk(oem_string_walker, &d); + + if (err) { + if (!IS_ERR_OR_NULL(d.value)) + kfree(d.value); + return ERR_PTR(err); + } + + return d.value; +} + +/* QCCFL357.0062.2020.0313.1530 -> 62 */ +static int __pure __init parse_bios_version(const char *str) +{ + const char *p = strchr(str, '.'), *p2; + int bios_version; + + if (!p) + return -EINVAL; + + p2 = strchr(p + 1, '.'); + + if (!p2) + return -EINVAL; + + p += 1; + + bios_version = 0; + + while (p != p2) { + if (!isdigit(*p)) + return -EINVAL; + + bios_version = 10 * bios_version + *p - '0'; + p += 1; + } + + return bios_version; +} + +static int __init check_features_ec(void) +{ + int err = ec_read_byte(SUPPORT_1_ADDR); + + if (err >= 0) { + qc71_features.super_key_lock = !!(err & SUPPORT_1_SUPER_KEY_LOCK); + qc71_features.lightbar = !!(err & SUPPORT_1_LIGHTBAR); + qc71_features.fan_boost = !!(err & SUPPORT_1_FAN_BOOST); + } else { + pr_warn("failed to query support_1 byte: %d\n", err); + } + + return err; +} + +static int __init check_features_bios(void) +{ + const char *bios_version_str; + int bios_version; + + if (!dmi_check_system(qc71_dmi_table)) { + pr_warn("no DMI match\n"); + return -ENODEV; + } + + bios_version_str = dmi_get_system_info(DMI_BIOS_VERSION); + + if (!bios_version_str) { + pr_warn("failed to get BIOS version DMI string\n"); + return -ENOENT; + } + + pr_info("BIOS version string: '%s'\n", bios_version_str); + + bios_version = parse_bios_version(bios_version_str); + + if (bios_version < 0) { + pr_warn("cannot parse BIOS version\n"); + return -EINVAL; + } + + pr_info("BIOS version: %04d\n", bios_version); + + if (bios_version >= 114) { + const char *s = read_oem_string(18); + size_t s_len; + + if (IS_ERR(s)) + return PTR_ERR(s); + + s_len = strlen(s); + + pr_info("OEM_STRING(18) = '%s'\n", s); + + /* if it is entirely spaces */ + if (strspn(s, " ") == s_len) { + qc71_features.fn_lock = true; + qc71_features.batt_charge_limit = true; + qc71_features.fan_extras = true; + } else if (s_len > 0) { + /* TODO */ + pr_warn("cannot extract supported features"); + } + + kfree(s); + } + + return 0; +} + +int __init qc71_check_features(void) +{ + (void) check_features_ec(); + (void) check_features_bios(); + + return 0; +} diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/features.h slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/features.h --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/features.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/features.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_FEATURES_H +#define QC71_FEATURES_H + +#include +#include + +struct qc71_features_struct { + bool super_key_lock : 1; + bool lightbar : 1; + bool fan_boost : 1; + bool fn_lock : 1; + bool batt_charge_limit : 1; + bool fan_extras : 1; /* duty cycle reduction, always on mode */ + bool silent_mode : 1; /* Slimbook silent mode: decreases fan rpm limit and tdp */ +}; + +/* ========================================================================== */ + +extern struct qc71_features_struct qc71_features; + +/* ========================================================================== */ + +int __init qc71_check_features(void); + +#endif /* QC71_FEATURES_H */ diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon.c slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon.c --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include + +#include "hwmon_fan.h" +#include "hwmon_pwm.h" + +/* ========================================================================== */ + +static bool nohwmon; +module_param(nohwmon, bool, 0444); +MODULE_PARM_DESC(nohwmon, "do not report to the hardware monitoring subsystem (default=false)"); + +/* ========================================================================== */ + +int __init qc71_hwmon_setup(void) +{ + if (nohwmon) + return -ENODEV; + + (void) qc71_hwmon_fan_setup(); + (void) qc71_hwmon_pwm_setup(); + + return 0; +} + +void qc71_hwmon_cleanup(void) +{ + (void) qc71_hwmon_fan_cleanup(); + (void) qc71_hwmon_pwm_cleanup(); +} diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon_fan.c slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon_fan.c --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon_fan.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon_fan.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include +#include +#include + +#include "ec.h" +#include "fan.h" +#include "features.h" +#include "pdev.h" + +/* ========================================================================== */ + +static struct device *qc71_hwmon_fan_dev; + +/* ========================================================================== */ + +static umode_t qc71_hwmon_fan_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + case hwmon_fan_fault: + return 0444; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_label: + return 0444; + } + default: + break; + } + + return 0; +} + +static int qc71_hwmon_fan_read(struct device *device, enum hwmon_sensor_types type, + u32 attr, int channel, long *value) +{ + int err; + + switch (type) { + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + err = qc71_fan_get_rpm(channel); + if (err < 0) + return err; + + *value = err; + break; + default: + return -EOPNOTSUPP; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + err = qc71_fan_get_temp(channel); + if (err < 0) + return err; + + *value = err * 1000; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int qc71_hwmon_fan_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + static const char * const temp_labels[] = { + "fan1_temp", + "fan2_temp", + }; + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_label: + *str = temp_labels[channel]; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +/* ========================================================================== */ + +static const struct hwmon_channel_info *qc71_hwmon_fan_ch_info[] = { + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT, + HWMON_F_INPUT), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + NULL +}; + +static const struct hwmon_ops qc71_hwmon_fan_ops = { + .is_visible = qc71_hwmon_fan_is_visible, + .read = qc71_hwmon_fan_read, + .read_string = qc71_hwmon_fan_read_string, +}; + +static const struct hwmon_chip_info qc71_hwmon_fan_chip_info = { + .ops = &qc71_hwmon_fan_ops, + .info = qc71_hwmon_fan_ch_info, +}; + +/* ========================================================================== */ + +int __init qc71_hwmon_fan_setup(void) +{ + int err = 0; + + qc71_hwmon_fan_dev = hwmon_device_register_with_info( + &qc71_platform_dev->dev, KBUILD_MODNAME ".hwmon.fan", NULL, + &qc71_hwmon_fan_chip_info, NULL); + + if (IS_ERR(qc71_hwmon_fan_dev)) + err = PTR_ERR(qc71_hwmon_fan_dev); + + return err; +} + +void qc71_hwmon_fan_cleanup(void) +{ + if (!IS_ERR_OR_NULL(qc71_hwmon_fan_dev)) + hwmon_device_unregister(qc71_hwmon_fan_dev); +} diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon_fan.h slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon_fan.h --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon_fan.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon_fan.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_HWMON_FAN_H +#define QC71_HWMON_FAN_H + +#include + +int __init qc71_hwmon_fan_setup(void); +void qc71_hwmon_fan_cleanup(void); + +#endif /* QC71_HWMON_FAN_H */ diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon.h slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon.h --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_HWMON_H +#define QC71_HWMON_H + +#if IS_ENABLED(CONFIG_HWMON) + +#include + +int __init qc71_hwmon_setup(void); +void qc71_hwmon_cleanup(void); + +#else + +static inline int qc71_hwmon_setup(void) +{ + return 0; +} + +static inline void qc71_hwmon_cleanup(void) +{ + +} + +#endif + +#endif /* QC71_HWMON_H */ diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon_pwm.c slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon_pwm.c --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon_pwm.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon_pwm.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fan.h" +#include "features.h" +#include "pdev.h" +#include "util.h" + +/* ========================================================================== */ + +static struct device *qc71_hwmon_pwm_dev; + +/* ========================================================================== */ + +static umode_t qc71_hwmon_pwm_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type != hwmon_pwm && attr != hwmon_pwm_enable) + return -EOPNOTSUPP; + + return 0644; +} + +static int qc71_hwmon_pwm_read(struct device *device, enum hwmon_sensor_types type, + u32 attr, int channel, long *value) +{ + int err; + + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_enable: + err = qc71_fan_get_mode(); + if (err < 0) + return err; + + *value = err; + break; + case hwmon_pwm_input: + err = qc71_fan_get_pwm(channel); + if (err < 0) + return err; + + *value = err; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int qc71_hwmon_pwm_write(struct device *device, enum hwmon_sensor_types type, + u32 attr, int channel, long value) +{ + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_enable: + return qc71_fan_set_mode(value); + case hwmon_pwm_input: + return qc71_fan_set_pwm(channel, value); + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static const struct hwmon_channel_info *qc71_hwmon_pwm_ch_info[] = { + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE), + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT, HWMON_PWM_INPUT), + NULL +}; + +static const struct hwmon_ops qc71_hwmon_pwm_ops = { + .is_visible = qc71_hwmon_pwm_is_visible, + .read = qc71_hwmon_pwm_read, + .write = qc71_hwmon_pwm_write, +}; + +static const struct hwmon_chip_info qc71_hwmon_pwm_chip_info = { + .ops = &qc71_hwmon_pwm_ops, + .info = qc71_hwmon_pwm_ch_info, +}; + +/* ========================================================================== */ + +int __init qc71_hwmon_pwm_setup(void) +{ + int err = 0; + + if (!qc71_features.fan_boost) + return -ENODEV; + + qc71_hwmon_pwm_dev = hwmon_device_register_with_info( + &qc71_platform_dev->dev, KBUILD_MODNAME ".hwmon.pwm", NULL, + &qc71_hwmon_pwm_chip_info, NULL); + + if (IS_ERR(qc71_hwmon_pwm_dev)) + err = PTR_ERR(qc71_hwmon_pwm_dev); + + return err; +} + +void qc71_hwmon_pwm_cleanup(void) +{ + if (!IS_ERR_OR_NULL(qc71_hwmon_pwm_dev)) + hwmon_device_unregister(qc71_hwmon_pwm_dev); +} diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon_pwm.h slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon_pwm.h --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon_pwm.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/hwmon_pwm.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_HWMON_PWM_H +#define QC71_HWMON_PWM_H + +#include + +int __init qc71_hwmon_pwm_setup(void); +void qc71_hwmon_pwm_cleanup(void); + +#endif /* QC71_HWMON_PWM_H */ diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/led_lightbar.c slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/led_lightbar.c --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/led_lightbar.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/led_lightbar.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +/* #include */ +#include +#include +#include + +#include "util.h" +#include "ec.h" +#include "features.h" +#include "led_lightbar.h" +#include "pdev.h" + +/* ========================================================================== */ + +#if IS_ENABLED(CONFIG_LEDS_CLASS) + +enum qc71_lightbar_color { + LIGHTBAR_RED = 0, + LIGHTBAR_GREEN = 1, + LIGHTBAR_BLUE = 2, + LIGHTBAR_COLOR_COUNT +}; + +static const uint16_t lightbar_color_addrs[LIGHTBAR_COLOR_COUNT] = { + [LIGHTBAR_RED] = LIGHTBAR_RED_ADDR, + [LIGHTBAR_GREEN] = LIGHTBAR_GREEN_ADDR, + [LIGHTBAR_BLUE] = LIGHTBAR_BLUE_ADDR, +}; + +static const uint8_t lightbar_colors[LIGHTBAR_COLOR_COUNT] = { + LIGHTBAR_RED, + LIGHTBAR_GREEN, + LIGHTBAR_BLUE, +}; + +/* f(x) = 4x */ +static const uint8_t lightbar_color_values[LIGHTBAR_COLOR_COUNT][10] = { + [LIGHTBAR_RED] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36}, + [LIGHTBAR_GREEN] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36}, + [LIGHTBAR_BLUE] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36}, +}; + +/* inverse of 'lightbar_color_values' */ +static const uint8_t lightbar_pwm_to_level[LIGHTBAR_COLOR_COUNT][256] = { + [LIGHTBAR_RED] = { + [0] = 0, + [4] = 1, + [8] = 2, + [12] = 3, + [16] = 4, + [20] = 5, + [24] = 6, + [28] = 7, + [32] = 8, + [36] = 9, + }, + + [LIGHTBAR_GREEN] = { + [0] = 0, + [4] = 1, + [8] = 2, + [12] = 3, + [16] = 4, + [20] = 5, + [24] = 6, + [28] = 7, + [32] = 8, + [36] = 9, + }, + + [LIGHTBAR_BLUE] = { + [0] = 0, + [4] = 1, + [8] = 2, + [12] = 3, + [16] = 4, + [20] = 5, + [24] = 6, + [28] = 7, + [32] = 8, + [36] = 9, + }, +}; + + +/* ========================================================================== */ + +static bool nolightbar; +module_param(nolightbar, bool, 0444); +MODULE_PARM_DESC(nolightbar, "do not register the lightbar to the leds subsystem (default=false)"); + +static bool lightbar_led_registered; + +/* ========================================================================== */ + +static inline int qc71_lightbar_get_status(void) +{ + return ec_read_byte(LIGHTBAR_CTRL_ADDR); +} + +static inline int qc71_lightbar_write_ctrl(uint8_t ctrl) +{ + return ec_write_byte(LIGHTBAR_CTRL_ADDR, ctrl); +} + +/* ========================================================================== */ + +static int qc71_lightbar_switch(uint8_t mask, bool on) +{ + int status; + + if (mask != LIGHTBAR_CTRL_S0_OFF && mask != LIGHTBAR_CTRL_S3_OFF) + return -EINVAL; + + status = qc71_lightbar_get_status(); + + if (status < 0) + return status; + + return qc71_lightbar_write_ctrl(SET_BIT(status, mask, !on)); +} + +static int qc71_lightbar_set_color_level(uint8_t color, uint8_t level) +{ + if (color >= ARRAY_SIZE(lightbar_color_addrs)) + return -EINVAL; + + if (level >= ARRAY_SIZE(lightbar_color_values[color])) + return -EINVAL; + + return ec_write_byte(lightbar_color_addrs[color], lightbar_color_values[color][level]); +} + +static int qc71_lightbar_get_color_level(uint8_t color) +{ + int err; + + if (color >= ARRAY_SIZE(lightbar_color_addrs)) + return -EINVAL; + + err = ec_read_byte(lightbar_color_addrs[color]); + if (err < 0) + return err; + + return lightbar_pwm_to_level[color][err]; +} + +static int qc71_lightbar_set_rainbow_mode(bool on) +{ + int status = qc71_lightbar_get_status(); + + if (status < 0) + return status; + + return qc71_lightbar_write_ctrl(SET_BIT(status, LIGHTBAR_CTRL_RAINBOW, on)); +} + +static int qc71_lightbar_set_color(unsigned int color) +{ + int err = 0, i; + + if (color > 999) /* color must lie in [0, 999] */ + return -EINVAL; + + for (i = ARRAY_SIZE(lightbar_colors) - 1; i >= 0 && !err; i--) + err = qc71_lightbar_set_color_level(lightbar_colors[i], + do_div(color, 10)); + + return err; +} + +/* ========================================================================== */ +/* lightbar attrs */ + +static ssize_t lightbar_s3_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int value = qc71_lightbar_get_status(); + + if (value < 0) + return value; + + return sprintf(buf, "%d\n", !(value & LIGHTBAR_CTRL_S3_OFF)); +} + +static ssize_t lightbar_s3_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + err = qc71_lightbar_switch(LIGHTBAR_CTRL_S3_OFF, value); + + if (err) + return err; + + return count; +} + +static ssize_t lightbar_color_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int color = 0; + size_t i; + + for (i = 0; i < ARRAY_SIZE(lightbar_colors); i++) { + int level = qc71_lightbar_get_color_level(lightbar_colors[i]); + + if (level < 0) + return level; + + color *= 10; + + if (0 <= level && level <= 9) + color += level; + } + + return sprintf(buf, "%03u\n", color); +} + +static ssize_t lightbar_color_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int value; + int err; + + if (kstrtouint(buf, 10, &value)) + return -EINVAL; + + err = qc71_lightbar_set_color(value); + if (err) + return err; + + return count; +} + +static ssize_t lightbar_rainbow_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = qc71_lightbar_get_status(); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", !!(status & LIGHTBAR_CTRL_RAINBOW)); +} + +static ssize_t lightbar_rainbow_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + err = qc71_lightbar_set_rainbow_mode(value); + + if (err) + return err; + + return count; +} + +static enum led_brightness qc71_lightbar_led_get_brightness(struct led_classdev *led_cdev) +{ + int err = qc71_lightbar_get_status(); + + if (err) + return 0; + + return !(err & LIGHTBAR_CTRL_S0_OFF); +} + +static int qc71_lightbar_led_set_brightness(struct led_classdev *led_cdev, + enum led_brightness value) +{ + return qc71_lightbar_switch(LIGHTBAR_CTRL_S0_OFF, !!value); +} + +#if 0 +static int qc71_lightbar_led_set_brightness(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); + unsigned int color = 0, i; + int err; + + led_mc_calc_color_components(led_mc_cdev, brightness); + + for (i = 0; i < led_mc_cdev->num_colors; i++) { + if (led_mc_cdev->subled_info[i].brightness > 9) + return -EINVAL; + + color = 10 * color + led_mc_cdev->subled_info[i].brightness; + } + + if (color) { + err = qc71_lightbar_switch(LIGHTBAR_CTRL_S0_OFF, 1); + + if (err) + goto out; + + err = qc71_lightbar_set_color(color); + } else { + err = qc71_lightbar_switch(LIGHTBAR_CTRL_S0_OFF, 0); + } + +out: + return err; +} +#endif + +/* ========================================================================== */ + +static DEVICE_ATTR(brightness_s3, 0644, lightbar_s3_show, lightbar_s3_store); +static DEVICE_ATTR(color, 0644, lightbar_color_show, lightbar_color_store); +static DEVICE_ATTR(rainbow_mode, 0644, lightbar_rainbow_show, lightbar_rainbow_store); + +static struct attribute *qc71_lightbar_led_attrs[] = { + &dev_attr_brightness_s3.attr, + &dev_attr_color.attr, + &dev_attr_rainbow_mode.attr, + NULL +}; + +ATTRIBUTE_GROUPS(qc71_lightbar_led); + +static struct led_classdev qc71_lightbar_led = { + .name = KBUILD_MODNAME "::lightbar", + .max_brightness = 1, + .brightness_get = qc71_lightbar_led_get_brightness, + .brightness_set_blocking = qc71_lightbar_led_set_brightness, + .groups = qc71_lightbar_led_groups, +}; + +#if 0 +static struct mc_subled qc71_lightbar_subleds[LIGHTBAR_COLOR_COUNT] = { + [LIGHTBAR_RED] = { + .color_index = LED_COLOR_ID_RED, + }, + [LIGHTBAR_GREEN] = { + .color_index = LED_COLOR_ID_GREEN, + }, + [LIGHTBAR_BLUE] = { + .color_index = LED_COLOR_ID_BLUE, + }, +}; + +static struct led_classdev_mc qc71_lightbar_led = { + .num_colors = ARRAY_SIZE(qc71_lightbar_subleds), + .subled_info = qc71_lightbar_subleds, + .led_cdev = { + .name = KBUILD_MODNAME "::lightbar", + .max_brightness = 9, + .brightness_set_blocking = qc71_lightbar_led_set_brightness, + }, +}; +#endif + +/* ========================================================================== */ + +int __init qc71_led_lightbar_setup(void) +{ + int err; + + if (nolightbar || !qc71_features.lightbar) + return -ENODEV; + +#if 0 + err = led_classdev_multicolor_register(&qc71_platform_dev->dev, &qc71_lightbar_led); +#endif + err = led_classdev_register(&qc71_platform_dev->dev, &qc71_lightbar_led); + + if (!err) + lightbar_led_registered = true; + +#if 0 + err = device_add_groups(qc71_lightbar_led.led_cdev.dev, qc71_lightbar_led_groups); + if (err) + led_classdev_multicolor_unregister(&qc71_lightbar_led); +#endif + + return err; +} + +void qc71_led_lightbar_cleanup(void) +{ + if (lightbar_led_registered) { +#if 0 + device_remove_groups(qc71_lightbar_led.led_cdev.dev, qc71_lightbar_led_groups); + led_classdev_multicolor_unregister(&qc71_lightbar_led); +#endif + led_classdev_unregister(&qc71_lightbar_led); + } +} + +#endif diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/led_lightbar.h slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/led_lightbar.h --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/led_lightbar.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/led_lightbar.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_LED_LIGHTBAR_H +#define QC71_LED_LIGHTBAR_H + +#if IS_ENABLED(CONFIG_LEDS_CLASS) + +#include + +int __init qc71_led_lightbar_setup(void); +void qc71_led_lightbar_cleanup(void); + +#else + +static inline int qc71_led_lightbar_setup(void) +{ + return 0; +} + +static inline void qc71_led_lightbar_cleanup(void) +{ + +} + +#endif + +#endif /* QC71_LED_LIGHTBAR_H */ diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/LICENSE slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/LICENSE --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/LICENSE 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/LICENSE 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/main.c slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/main.c --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/main.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/main.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0 +/* ========================================================================== */ +/* https://www.intel.com/content/dam/support/us/en/documents/laptops/whitebook/QC71_PROD_SPEC.pdf + * + * + * based on the following resources: + * - https://lwn.net/Articles/391230/ + * - http://blog.nietrzeba.pl/2011/12/mof-decompilation.html + * - https://github.com/tuxedocomputers/tuxedo-cc-wmi/ + * - https://github.com/tuxedocomputers/tuxedo-keyboard/ + * - Control Center for Microsoft Windows + * - http://forum.notebookreview.com/threads/tongfang-gk7cn6s-gk7cp0s-gk7cp7s.825461/page-54 + */ +/* ========================================================================== */ +#include "pr.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "ec.h" +#include "features.h" +#include "wmi.h" + +/* submodules */ +#include "pdev.h" +#include "events.h" +#include "hwmon.h" +#include "battery.h" +#include "led_lightbar.h" +#include "debugfs.h" + +/* ========================================================================== */ + +#define SUBMODULE_ENTRY(_name, _req) { .name = #_name, .init = qc71_ ## _name ## _setup, .cleanup = qc71_ ## _name ## _cleanup, .required = _req } + +static struct qc71_submodule { + const char *name; + + bool required : 1, + initialized : 1; + + int (*init)(void); + void (*cleanup)(void); +} qc71_submodules[] __refdata = { + SUBMODULE_ENTRY(pdev, true), /* must be first */ + SUBMODULE_ENTRY(wmi_events, false), + SUBMODULE_ENTRY(hwmon, false), + SUBMODULE_ENTRY(battery, false), + SUBMODULE_ENTRY(led_lightbar, false), + SUBMODULE_ENTRY(debugfs, false), +}; + +#undef SUBMODULE_ENTRY + +static void do_cleanup(void) +{ + int i; + + for (i = ARRAY_SIZE(qc71_submodules) - 1; i >= 0; i--) { + const struct qc71_submodule *sm = &qc71_submodules[i]; + + if (sm->initialized) + sm->cleanup(); + } +} + +static int __init qc71_laptop_module_init(void) +{ + int err = 0, i; + + if (!wmi_has_guid(QC71_WMI_WMBC_GUID)) { + pr_err("WMI GUID not found\n"); + err = -ENODEV; goto out; + } + + err = ec_read_byte(PROJ_ID_ADDR); + if (err < 0) { + pr_err("failed to query project id: %d\n", err); + goto out; + } + + pr_info("project id: %d\n", err); + + err = ec_read_byte(PLATFORM_ID_ADDR); + if (err < 0) { + pr_err("failed to query platform id: %d\n", err); + goto out; + } + + pr_info("platform id: %d\n", err); + + err = qc71_check_features(); + if (err) { + pr_err("cannot check system features: %d\n", err); + goto out; + } + + pr_info("supported features:"); + if (qc71_features.super_key_lock) pr_cont(" super-key-lock"); + if (qc71_features.lightbar) pr_cont(" lightbar"); + if (qc71_features.fan_boost) pr_cont(" fan-boost"); + if (qc71_features.fn_lock) pr_cont(" fn-lock"); + if (qc71_features.batt_charge_limit) pr_cont(" charge-limit"); + if (qc71_features.fan_extras) pr_cont(" fan-extras"); + if (qc71_features.silent_mode) pr_cont(" silent-mode"); + + pr_cont("\n"); + + for (i = 0; i < ARRAY_SIZE(qc71_submodules); i++) { + struct qc71_submodule *sm = &qc71_submodules[i]; + + err = sm->init(); + if (err) { + pr_warn("failed to initialize %s submodule: %d\n", sm->name, err); + if (sm->required) + goto out; + } else { + sm->initialized = true; + } + } + + err = 0; + +out: + if (err) + do_cleanup(); + else + pr_info("module loaded\n"); + + return err; +} + +static void __exit qc71_laptop_module_cleanup(void) +{ + do_cleanup(); + pr_info("module unloaded\n"); +} + +/* ========================================================================== */ + +module_init(qc71_laptop_module_init); +module_exit(qc71_laptop_module_cleanup); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Barnabás Pőcze "); +MODULE_DESCRIPTION("QC71 laptop platform driver"); +MODULE_ALIAS("wmi:" QC71_WMI_WMBC_GUID); diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/Makefile slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/Makefile --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/Makefile 2022-08-17 10:06:50.000000000 +0000 @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0 +MODNAME = qc71_laptop +MODVER = 0.0 + +obj-m += $(MODNAME).o + +# alphabetically sorted +$(MODNAME)-y += ec.o \ + features.o \ + main.o \ + misc.o \ + pdev.o \ + events.o \ + +$(MODNAME)-$(CONFIG_DEBUG_FS) += debugfs.o +$(MODNAME)-$(CONFIG_ACPI_BATTERY) += battery.o +$(MODNAME)-$(CONFIG_LEDS_CLASS) += led_lightbar.o +$(MODNAME)-$(CONFIG_HWMON) += hwmon.o hwmon_fan.o hwmon_pwm.o fan.o + +KVER = $(shell uname -r) +KDIR = /lib/modules/$(KVER)/build +MDIR = /usr/src/$(MODNAME)-$(MODVER) + +all: + make -C $(KDIR) M=$(PWD) modules + +clean: + make -C $(KDIR) M=$(PWD) clean + +dkmsinstall: + mkdir -p $(MDIR) + cp Makefile dkms.conf $(wildcard *.c) $(wildcard *.h) $(MDIR)/. + dkms add $(MODNAME)/$(MODVER) + dkms build $(MODNAME)/$(MODVER) + dkms install $(MODNAME)/$(MODVER) + +dkmsuninstall: + -rmmod $(MODNAME) + -dkms uninstall $(MODNAME)/$(MODVER) + -dkms remove $(MODNAME)/$(MODVER) --all + rm -rf $(MDIR) diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/misc.c slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/misc.c --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/misc.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/misc.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include + +#include "ec.h" +#include "misc.h" +#include "util.h" + +/* ========================================================================== */ + +int qc71_rfkill_get_wifi_state(void) +{ + int err = ec_read_byte(DEVICE_STATUS_ADDR); + + if (err < 0) + return err; + + return !!(err & DEVICE_STATUS_WIFI_ON); +} + +/* ========================================================================== */ + +int qc71_fn_lock_get_state(void) +{ + int status = ec_read_byte(BIOS_CTRL_1_ADDR); + + if (status < 0) + return status; + + return !!(status & BIOS_CTRL_1_FN_LOCK_STATUS); +} + +int qc71_fn_lock_set_state(bool state) +{ + int status = ec_read_byte(BIOS_CTRL_1_ADDR); + + if (status < 0) + return status; + + status = SET_BIT(status, BIOS_CTRL_1_FN_LOCK_STATUS, state); + + return ec_write_byte(BIOS_CTRL_1_ADDR, status); +} diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/misc.h slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/misc.h --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/misc.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/misc.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_MISC_H +#define QC71_MISC_H + +#include + +/* ========================================================================== */ + +int qc71_rfkill_get_wifi_state(void); + +int qc71_fn_lock_get_state(void); +int qc71_fn_lock_set_state(bool state); + +#endif /* QC71_MISC_H */ diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/pdev.c slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/pdev.c --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/pdev.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/pdev.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include +#include + +#include "util.h" +#include "ec.h" +#include "features.h" +#include "misc.h" +#include "pdev.h" + +/* ========================================================================== */ + +struct platform_device *qc71_platform_dev; + +/* ========================================================================== */ + +static ssize_t fan_reduced_duty_cycle_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = ec_read_byte(BIOS_CTRL_3_ADDR); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", !!(status & BIOS_CTRL_3_FAN_REDUCED_DUTY_CYCLE)); +} + +static ssize_t fan_reduced_duty_cycle_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + status = ec_read_byte(BIOS_CTRL_3_ADDR); + if (status < 0) + return status; + + status = SET_BIT(status, BIOS_CTRL_3_FAN_REDUCED_DUTY_CYCLE, value); + + status = ec_write_byte(BIOS_CTRL_3_ADDR, status); + + if (status < 0) + return status; + + return count; +} + +static ssize_t fan_always_on_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = ec_read_byte(BIOS_CTRL_3_ADDR); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", !!(status & BIOS_CTRL_3_FAN_ALWAYS_ON)); +} + +static ssize_t fan_always_on_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + status = ec_read_byte(BIOS_CTRL_3_ADDR); + if (status < 0) + return status; + + status = SET_BIT(status, BIOS_CTRL_3_FAN_ALWAYS_ON, value); + + status = ec_write_byte(BIOS_CTRL_3_ADDR, status); + + if (status < 0) + return status; + + return count; +} + +static ssize_t fn_lock_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = qc71_fn_lock_get_state(); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", status); +} + +static ssize_t fn_lock_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + status = qc71_fn_lock_set_state(value); + if (status < 0) + return status; + + return count; +} + +static ssize_t fn_lock_switch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = ec_read_byte(AP_BIOS_BYTE_ADDR); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", !!(status & AP_BIOS_BYTE_FN_LOCK_SWITCH)); +} + +static ssize_t fn_lock_switch_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + status = ec_read_byte(AP_BIOS_BYTE_ADDR); + if (status < 0) + return status; + + status = SET_BIT(status, AP_BIOS_BYTE_FN_LOCK_SWITCH, value); + + status = ec_write_byte(AP_BIOS_BYTE_ADDR, status); + + if (status < 0) + return status; + + return count; +} + +static ssize_t manual_control_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = ec_read_byte(CTRL_1_ADDR); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", !!(status & CTRL_1_MANUAL_MODE)); +} + +static ssize_t manual_control_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + status = ec_read_byte(CTRL_1_ADDR); + if (status < 0) + return status; + + status = SET_BIT(status, CTRL_1_MANUAL_MODE, value); + + status = ec_write_byte(CTRL_1_ADDR, status); + + if (status < 0) + return status; + + return count; +} + +static ssize_t super_key_lock_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = ec_read_byte(STATUS_1_ADDR); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", !!(status & STATUS_1_SUPER_KEY_LOCK)); +} + +static ssize_t super_key_lock_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + status = ec_read_byte(STATUS_1_ADDR); + if (status < 0) + return status; + + if (value != !!(status & STATUS_1_SUPER_KEY_LOCK)) { + status = ec_write_byte(TRIGGER_1_ADDR, TRIGGER_1_SUPER_KEY_LOCK); + + if (status < 0) + return status; + } + + return count; +} + +static ssize_t fan_boost_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = ec_read_byte(FAN_CTRL_ADDR); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", !!(status & FAN_CTRL_FAN_BOOST)); +} + +static ssize_t fan_boost_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + status = ec_read_byte(FAN_CTRL_ADDR); + if (status < 0) + return status; + + if (value != !!(status & FAN_CTRL_FAN_BOOST)) { + status = ec_write_byte(TRIGGER_1_ADDR, TRIGGER_1_FAN_BOOST); + + if (status < 0) + return status; + } + + return count; +} + +static ssize_t silent_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = ec_read_byte(FAN_CTRL_ADDR); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", !!(status & FAN_CTRL_SILENT_MODE)); +} + +static ssize_t silent_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + status = ec_read_byte(FAN_CTRL_ADDR); + if (status < 0) + return status; + + status = SET_BIT(status, FAN_CTRL_SILENT_MODE, value); + + status = ec_write_byte(FAN_CTRL_ADDR, status); + + if (status < 0) + return status; + + return count; +} + +/* ========================================================================== */ + +static DEVICE_ATTR_RW(fn_lock); +static DEVICE_ATTR_RW(fn_lock_switch); +static DEVICE_ATTR_RW(fan_always_on); +static DEVICE_ATTR_RW(fan_reduced_duty_cycle); +static DEVICE_ATTR_RW(manual_control); +static DEVICE_ATTR_RW(super_key_lock); +static DEVICE_ATTR_RW(fan_boost); +static DEVICE_ATTR_RW(silent_mode); + +static struct attribute *qc71_laptop_attrs[] = { + &dev_attr_fn_lock.attr, + &dev_attr_fn_lock_switch.attr, + &dev_attr_fan_always_on.attr, + &dev_attr_fan_reduced_duty_cycle.attr, + &dev_attr_manual_control.attr, + &dev_attr_super_key_lock.attr, + &dev_attr_fan_boost.attr, + &dev_attr_silent_mode.attr, + NULL +}; + +/* ========================================================================== */ + +static umode_t qc71_laptop_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + bool ok = false; + + if (attr == &dev_attr_fn_lock.attr || attr == &dev_attr_fn_lock_switch.attr) + ok = qc71_features.fn_lock; + else if (attr == &dev_attr_fan_always_on.attr || attr == &dev_attr_fan_reduced_duty_cycle.attr) + ok = qc71_features.fan_extras; + else if (attr == &dev_attr_manual_control.attr) + ok = true; + else if (attr == &dev_attr_super_key_lock.attr) + ok = qc71_features.super_key_lock; + else if (attr == &dev_attr_fan_boost.attr) + ok = qc71_features.fan_boost; + else if (attr == &dev_attr_silent_mode.attr) + ok = qc71_features.silent_mode; + + return ok ? attr->mode : 0; +} + +/* ========================================================================== */ + +static const struct attribute_group qc71_laptop_group = { + .is_visible = qc71_laptop_attr_is_visible, + .attrs = qc71_laptop_attrs, +}; + +static const struct attribute_group *qc71_laptop_groups[] = { + &qc71_laptop_group, + NULL +}; + +/* ========================================================================== */ + +int __init qc71_pdev_setup(void) +{ + int err; + + qc71_platform_dev = platform_device_alloc(KBUILD_MODNAME, PLATFORM_DEVID_NONE); + if (!qc71_platform_dev) { + err = -ENOMEM; + goto out; + } + + qc71_platform_dev->dev.groups = qc71_laptop_groups; + + err = platform_device_add(qc71_platform_dev); + if (err) { + platform_device_put(qc71_platform_dev); + qc71_platform_dev = NULL; + } + +out: + return err; +} + +void qc71_pdev_cleanup(void) +{ + /* checks for IS_ERR_OR_NULL() */ + platform_device_unregister(qc71_platform_dev); +} diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/pdev.h slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/pdev.h --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/pdev.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/pdev.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_PDEV_H +#define QC71_PDEV_H + +#include +#include + +/* ========================================================================== */ + +extern struct platform_device *qc71_platform_dev; + +/* ========================================================================== */ + +int __init qc71_pdev_setup(void); +void qc71_pdev_cleanup(void); + +#endif /* QC71_PDEV_H */ diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/pr.h slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/pr.h --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/pr.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/pr.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_PR_H +#define QC71_PR_H + +#define pr_fmt(fmt) KBUILD_MODNAME "/" KBUILD_BASENAME ": %s: " fmt, __func__ + +#include + +#endif /* QC71_PR_H */ diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/README.md slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/README.md --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/README.md 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/README.md 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,230 @@ +# What is it? +This a Linux kernel platform driver for Intel Whitebook LAPQC71X systems (XMG Fusion 15, Eluktronics MAG 15, Aftershock Vapor 15, ...). + + +# Disclaimer +**This software is in early stages of developement. Futhermore, to quote GPL: everything is provided as is. There is no warranty for the program, to the extent permitted by applicable law.** + +**This software is licensed under the GNU General Public License v2.0** + + +# Compatibility +It has only been tested on an XMG Fusion 15 device (BIOS 0062 up to 0120) and with the `5.4`, and `5.8`-`5.13` kernel series. +Some functions have been confirmed to work on the Tongfang GK7C chassis (XMG Neo 17, PCS Recoil III, Walmart OP17) (see [#6][issue6]). + + +# Dependencies +### Required +* Your kernel has been compiled with `CONFIG_ACPI` and `CONFIG_DMI` (it probably was) +* Linux headers for you current kernel + +### Optional +* DKMS if you don't want to recompile it manually every time the kernel is upgraded + + +# Features +## Current +* Integrate fan speeds into the Linux hardware monitoring subsystem (so that `lm_sensors` can pick it up) +* Control the lightbar +* Enable/disable always-on mode, reduced fan duty cycle (BIOS 0114 and above) +* Fn lock (BIOS 0114 and above) +* Change battery charge limit (BIOS 0114 and above) + + +# How to install +## Downloading +If you have `git` installed: +``` +git clone https://github.com/pobrn/qc71_laptop +``` + +If you don't, then you can download it [here](https://github.com/pobrn/qc71_laptop/archive/master.zip). + +## Installing +### Linux headers +On Debian and its [many][debian-derivatives] [derivatives][ubuntu-derivatives] (Ubuntu, Pop OS, Linux Mint, ...) , run +``` +sudo apt install linux-headers-$(uname -r) +``` +to install the necessary header files. + +On Arch Linux and its derivatives (Manjaro, ...), run +``` +sudo pacman -Syu linux-headers +``` + +### DKMS (optional) +DKMS should be in your distributions repositories. `sudo apt install dkms`, `sudo pacman -Syu dkms` should work depending on your distribution. + +### The module +#### Manually +Navigate in a terminal into the directory, then execute `make`. This should compile the module. If everything went correctly, a file named `qc71_laptop.ko` should appear in the directory. + +To test the module try `sudo insmod qc71_laptop.ko`. Now you should see the fan speeds appear in the output of `sensors`, and the directory `/sys/devices/platform/qc71_laptop` should now exist. If you are done testing, unload the module using `sudo rmmod qc71_laptop`. + +Now you could create a script that inserts this module at boot from this directory, or you could install it using DKMS. + +#### With DKMS +Run +``` +sudo make dkmsinstall +``` +to install the module with DKMS. Or run +``` +sudo make dkmsuninstall +``` +to uninstall the module. + +The module should automatically load at boot after this. If you want to load it immediately, run `sudo modprobe qc71_laptop`. If it reports an error, and you're convinced your device should be supported, please open an [issue][issues]. + +## Upgrade + +If you installed the module with DKMS, and you wish to upgrade, first open the directory of the old sources, and run +``` +sudo make dkmsuninstall +``` +then update the sources (pull the repository, download the sources again manually, etc.), then run +``` +sudo make dkmsinstall +``` + + +# How to use +## Fan speeds +After loading the module the fan speeds and temperatures should immediately appear in the output of `sensors`, and all your favourite monitoring utilities (e.g. the [Freon][gnome-ext-freon] GNOME shell extension) that use `sensors`. + +## Controlling the lightbar +The lightbar is integrated into the LED subsystem of the linux kernel. When the module is loaded, `/sys/class/leds/qc71_laptop::lightbar` directory should exist with the following important files: +``` +/sys/class/leds/qc71_laptop::lightbar/brightness +``` + +It contains `1` if the lightbar is turned on in S0 sleep state (aka. when the device is powered on), `0` otherwise. You can turn on/off the lightbar by writing an appropriate number into this file: +``` +# echo 1 > /sys/class/leds/qc71_laptop::lightbar/brightness +``` +will turn the lightbar on. (Writing `0` will turn it off.) +To check the current state: +``` +$ cat /sys/class/leds/qc71_laptop::lightbar/brightness +``` + +___ +``` +/sys/class/leds/qc71_laptop::lightbar/brightness_s3 +``` +It contains `1` if the lightbar is turned on in S3 sleep state (aka. when the device is sleeping). If it is `1`, the lightbar will "breathe" when the device is sleeping. You can control it the same way as `brightness`. + +___ +``` +/sys/class/leds/qc71_laptop::lightbar/rainbow_mode +``` +It contains `1` if the "rainbow mode" is enabled (aka. the color will continuously cycle). Controlling works the same way as before. + +*Note:* Enabling/disabling the rainbow mode will not turn the lightbar on/off. +*Note:* The rainbow mode takes precedence over the color. + +___ +``` +/sys/class/leds/qc71_laptop::lightbar/color +``` +This file controls the color of the lightbar. It is a three digit number (possibly with padding zeroes). The first digit is the red component, the second one is the green componenet, the third one is the blue component. +``` +$ cat /sys/class/leds/qc71_laptop::lightbar/color +``` +tells you the current color, while +``` +# echo 591 > /sys/class/leds/qc71_laptop::lightbar/color +``` +will change the current color. + +*Note:* Chaning the color will not turn the lightbar on. + + +## Controlling the fans +These can be controlled directly from the BIOS as well. + +### Passive cooling +I call this feature "always on" because that's less confusing than "passive cooling". +``` +# echo 1 > /sys/devices/platform/qc71_laptop/fan_always_on +``` +will cause the fans to run continuously. Writing `0` will turn it off. + +### Reduced duty cycle +``` +# echo 1 > /sys/devices/platform/qc71_laptop/fan_reduced_duty_cycle +``` +will cause the fans to run at 25% of their capacity (about 2300 RPM) at idle (instead of 30% - about 2700 RPM). Writing `0` will restore the 30% idle duty cycle. + +## Fn lock +``` +# echo 1 > /sys/devices/platform/qc71_laptop/fn_lock_switch +``` +will enable changing the Fn lock state by pressing Fn+ESC. If this file contains `1`, then pressing Fn+ESC will toggle the Fn lock; if this file contains `0`, then pressing Fn+ESC will have no effect on the Fn lock. + +``` +# echo 1 > /sys/devices/platform/qc71_laptop/fn_lock +``` +will directly enable the Fn lock. If this file contains `1`, then pressing the functions keys will trigger their secondary functions (mute, brightness up, etc.); if this file contains `0`, then pressing the functions keys will trigger their primary functions (F1, F2, ...). + +## Battery charge limit +The file `/sys/class/power_supply/BAT0/charge_control_end_threshold` contains the current charge threshold. Writing a number between 1 and 100 will cause the battery charging limit to be set to that percentage. (I did not test extremely low values, so I cannot say if they work). For example: +``` +# echo 60 > /sys/class/power_supply/BAT0/charge_control_end_threshold +``` +will cause charging to stop when the battery reaches 60% of its capacity. + +## Super key (windows key) lock +It is possible to disable the super (windows) key by pressing Fn+F2 (or just F2 if the Fn lock is enabled). This can be also achieved by changing the writing the appropriate value into `/sys/devices/platform/qc71_laptop/super_key_lock`. +``` +# echo 1 > /sys/devices/platform/qc71_laptop/super_key_lock +``` +disables the super key, while +``` +# echo 0 > /sys/devices/platform/qc71_laptop/super_key_lock +``` +enables it. Reading the file will provide information about the current state of the super key. `0` means enabled, `1` means disabled. + +## Example use + +The XMG Control Center can change the color if the device is on battery or plugged in. Fortunately you can easily achieve the same using [acpid](https://wiki.archlinux.org/index.php/Acpid). Modifying the appropriate part of `/etc/acpi/handler.sh` like this: +``` + ac_adapter) + case "$2" in + AC|ACAD|ADP0|ACPI0003:00) # the "ACPI0003:00" part was not there by default + case "$4" in + 00000000) + logger 'AC unplugged' + + # change color to red + echo 900 > /sys/class/leds/qc71_laptop::lightbar/color + ;; + 00000001) + logger 'AC plugged' + + # change color to blue + echo 009 > /sys/class/leds/qc71_laptop::lightbar/color + ;; + esac + ;; + *) + logger "ACPI action undefined: $2" + ;; + esac + ;; +``` +You can use `acpi_listen` to see what events are generated when you plug the machine in or disconnect the charger. You might need to modify the third line (in this snippet). + + +# Troubleshooting + +* The [TUXEDO Control Center][tcc-github] may interfere with the operation of this kernel module. I do not recommend using both at the same time. + + +[issue6]: https://github.com/pobrn/qc71_laptop/issues/6 +[debian-derivatives]: https://www.debian.org/derivatives/ +[ubuntu-derivatives]: https://wiki.ubuntu.com/DerivativeTeam/Derivatives +[issues]: https://github.com/pobrn/qc71_laptop/issues +[gnome-ext-freon]: https://extensions.gnome.org/extension/841/freon/ +[tcc-github]: https://github.com/tuxedocomputers/tuxedo-control-center diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/util.h slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/util.h --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/util.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/util.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_UTIL_H +#define QC71_UTIL_H + +#define SET_BIT(value, bit, on) ((on) ? ((value) | (bit)) : ((value) & ~(bit))) + +#endif /* QC71_UTIL_H */ diff -Nru slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/wmi.h slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/wmi.h --- slimbook-qc71-0.1/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/wmi.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/debian/qc71-laptop-dkms/usr/src/qc71_laptop-0.1/wmi.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_LAPTOP_WMI_H +#define QC71_LAPTOP_WMI_H + +/* ========================================================================== */ +/* WMI methods */ + +/* AcpiTest_MULong */ +#define QC71_WMI_WMBC_GUID "ABBC0F6F-8EA1-11D1-00A0-C90629100000" +#define QC71_WMBC_GETSETULONG_ID 4 + +/* ========================================================================== */ +/* WMI events */ + +/* AcpiTest_EventULong */ +#define QC71_WMI_EVENT0_GUID "ABBC0F72-8EA1-11D1-00A0-C90629100000" + +/* AcpiTest_EventString */ +#define QC71_WMI_EVENT1_GUID "ABBC0F71-8EA1-11D1-00A0-C90629100000" + +/* AcpiTest_EventPackage */ +#define QC71_WMI_EVENT2_GUID "ABBC0F70-8EA1-11D1-00A0-C90629100000" + +#endif /* QC71_LAPTOP_WMI_H */ diff -Nru slimbook-qc71-0.1/debian/rules slimbook-qc71-0.2/debian/rules --- slimbook-qc71-0.1/debian/rules 2022-08-17 13:01:25.000000000 +0000 +++ slimbook-qc71-0.2/debian/rules 2022-08-18 13:19:13.000000000 +0000 @@ -4,9 +4,9 @@ # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 -DEB_NAME=slimbook-qc71 -NAME=slimbook-qc71 -VERSION=0.0 +DEB_NAME=qc71-laptop +NAME=qc71_laptop +VERSION=0.1 configure: configure-stamp configure-stamp: @@ -16,7 +16,7 @@ build: build-stamp -build-stamp: configure-stamp +build-stamp: configure-stamp dh_testdir $(MAKE) touch $@ @@ -33,7 +33,7 @@ dh_testroot dh_prep dh_installdirs - $(MAKE) DESTDIR=$(CURDIR)/debian/$(DEB_NAME) NAME=$(NAME) VERSION=$(VERSION) install + $(MAKE) DESTDIR=$(CURDIR)/debian/$(DEB_NAME)-dkms NAME=$(NAME) VERSION=$(VERSION) install binary-arch: build install diff -Nru slimbook-qc71-0.1/Makefile slimbook-qc71-0.2/Makefile --- slimbook-qc71-0.1/Makefile 2022-08-17 11:53:40.000000000 +0000 +++ slimbook-qc71-0.2/Makefile 2016-08-31 17:23:47.000000000 +0000 @@ -1,6 +1,6 @@ #/usr/bin/make SRC = $(DESTDIR)/usr/src -SHARE = $(DESTDIR)/usr/share/$(NAME) +SHARE = $(DESTDIR)/usr/share/$(NAME)-dkms all: diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/battery.c slimbook-qc71-0.2/qc71_laptop-0.1/battery.c --- slimbook-qc71-0.1/qc71_laptop-0.1/battery.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/battery.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "ec.h" +#include "features.h" + +/* ========================================================================== */ + +#if IS_ENABLED(CONFIG_ACPI_BATTERY) + +static bool battery_hook_registered; + +static bool nobattery; +module_param(nobattery, bool, 0444); +MODULE_PARM_DESC(nobattery, "do not expose battery related controls (default=false)"); + +/* ========================================================================== */ + +static ssize_t charge_control_end_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = ec_read_byte(BATT_CHARGE_CTRL_ADDR); + + if (status < 0) + return status; + + status &= BATT_CHARGE_CTRL_VALUE_MASK; + + if (status == 0) + status = 100; + + return sprintf(buf, "%d\n", status); +} + +static ssize_t charge_control_end_threshold_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status, value; + + if (kstrtoint(buf, 10, &value) || !(1 <= value && value <= 100)) + return -EINVAL; + + status = ec_read_byte(BATT_CHARGE_CTRL_ADDR); + if (status < 0) + return status; + + if (value == 100) + value = 0; + + status = (status & ~BATT_CHARGE_CTRL_VALUE_MASK) | value; + + status = ec_write_byte(BATT_CHARGE_CTRL_ADDR, status); + + if (status < 0) + return status; + + return count; +} + +static DEVICE_ATTR_RW(charge_control_end_threshold); +static struct attribute *qc71_laptop_batt_attrs[] = { + &dev_attr_charge_control_end_threshold.attr, + NULL +}; +ATTRIBUTE_GROUPS(qc71_laptop_batt); + +static int qc71_laptop_batt_add(struct power_supply *battery) +{ + if (strcmp(battery->desc->name, "BAT0") != 0) + return 0; + + return device_add_groups(&battery->dev, qc71_laptop_batt_groups); +} + +static int qc71_laptop_batt_remove(struct power_supply *battery) +{ + if (strcmp(battery->desc->name, "BAT0") != 0) + return 0; + + device_remove_groups(&battery->dev, qc71_laptop_batt_groups); + return 0; +} + +static struct acpi_battery_hook qc71_laptop_batt_hook = { + .add_battery = qc71_laptop_batt_add, + .remove_battery = qc71_laptop_batt_remove, + .name = "QC71 laptop battery extension", +}; + +int __init qc71_battery_setup(void) +{ + if (nobattery || !qc71_features.batt_charge_limit) + return -ENODEV; + + battery_hook_register(&qc71_laptop_batt_hook); + battery_hook_registered = true; + + return 0; +} + +void qc71_battery_cleanup(void) +{ + if (battery_hook_registered) + battery_hook_unregister(&qc71_laptop_batt_hook); +} + +#endif diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/battery.h slimbook-qc71-0.2/qc71_laptop-0.1/battery.h --- slimbook-qc71-0.1/qc71_laptop-0.1/battery.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/battery.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_BATTERY_H +#define QC71_BATTERY_H + +#if IS_ENABLED(CONFIG_ACPI_BATTERY) + +#include + +int __init qc71_battery_setup(void); +void qc71_battery_cleanup(void); + +#else + +static inline int qc71_battery_setup(void) +{ + return 0; +} + +static inline void qc71_battery_cleanup(void) +{ + +} + +#endif + +#endif /* QC71_BATTERY_H */ diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/debugfs.c slimbook-qc71-0.2/qc71_laptop-0.1/debugfs.c --- slimbook-qc71-0.1/qc71_laptop-0.1/debugfs.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/debugfs.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include +#include +#include + +#include "debugfs.h" +#include "ec.h" + +#if IS_ENABLED(CONFIG_DEBUG_FS) + +#define DEBUGFS_DIR_NAME KBUILD_MODNAME + +static const struct qc71_debugfs_attr { + const char *name; + uint16_t addr; +} qc71_debugfs_attrs[] = { + {"1108", 1108}, + + {"ap_bios_byte", AP_BIOS_BYTE_ADDR}, + + {"batt_alert", BATT_ALERT_ADDR}, + {"batt_charge_ctrl", BATT_CHARGE_CTRL_ADDR}, + {"batt_status", BATT_STATUS_ADDR}, + {"battt_temp", BATT_TEMP_ADDR}, + {"bios_ctrl_1", BIOS_CTRL_1_ADDR}, + {"bios_ctrl_2", BIOS_CTRL_2_ADDR}, + {"bios_ctrl_3", BIOS_CTRL_3_ADDR}, + {"bios_info_1", BIOS_INFO_1_ADDR}, + {"bios_info_5", BIOS_INFO_5_ADDR}, + + {"ctrl_1", CTRL_1_ADDR}, + {"ctrl_2", CTRL_2_ADDR}, + {"ctrl_3", CTRL_3_ADDR}, + {"ctrl_4", CTRL_4_ADDR}, + {"ctrl_5", CTRL_5_ADDR}, + {"ctrl_6", CTRL_6_ADDR}, + + {"device_status", DEVICE_STATUS_ADDR}, + + {"fan_ctrl", FAN_CTRL_ADDR}, + {"fan_mode_index", FAN_MODE_INDEX_ADDR}, + {"fan_temp_1", FAN_TEMP_1_ADDR}, + {"fan_temp_2", FAN_TEMP_2_ADDR}, + {"fan_pwm_1", FAN_PWM_1_ADDR}, + {"fan_pwm_2", FAN_PWM_2_ADDR}, + + /* setting these don't seem to work */ + {"fan_l1_pwm", ADDR(0x07, 0x43)}, + {"fan_l2_pwm", ADDR(0x07, 0x44)}, + {"fan_l3_pwm", ADDR(0x07, 0x45)}, + /* seemingly there is another level here, fan_ctrl=0x84, pwm=0x5a */ + {"fan_l4_pwm", ADDR(0x07, 0x46)}, + {"fan_l5_pwm", ADDR(0x07, 0x47)}, /* this is seemingly ignored, fan_ctrl=0x86, pwm=0xb4 */ + + {"fan_l1_pwm_default", ADDR(0x07, 0x86)}, + {"fan_l2_pwm_default", ADDR(0x07, 0x87)}, + {"fan_l3_pwm_default", ADDR(0x07, 0x88)}, + {"fan_l4_pwm_default", ADDR(0x07, 0x89)}, + {"fan_l5_pwm_default", ADDR(0x07, 0x8A)}, + + /* these don't seem to work */ + {"fan_min_speed", 1950}, + {"fan_min_temp", 1951}, + {"fan_extra_speed", 1952}, + + {"lightbar_ctrl", LIGHTBAR_CTRL_ADDR}, + {"lightbar_red", LIGHTBAR_RED_ADDR}, + {"lightbar_green", LIGHTBAR_GREEN_ADDR}, + {"lightbar_blue", LIGHTBAR_BLUE_ADDR}, + + {"keyboard_type", KEYBOARD_TYPE_ADDR}, + + {"support_1", SUPPORT_1_ADDR}, + {"support_2", SUPPORT_2_ADDR}, + {"support_5", SUPPORT_5_ADDR}, + {"status_1", STATUS_1_ADDR}, + + {"platform_id", PLATFORM_ID_ADDR}, + {"power_source", POWER_SOURCE_ADDR}, + {"project_id", PROJ_ID_ADDR}, + {"power_status", POWER_STATUS_ADDR}, + {"pl_1", PL1_ADDR}, + {"pl_2", PL2_ADDR}, + {"pl_4", PL4_ADDR}, + + {"trigger_1", TRIGGER_1_ADDR}, + {"trigger_2", TRIGGER_2_ADDR}, +}; + +/* ========================================================================== */ + +static bool debugregs; +module_param(debugregs, bool, 0444); +MODULE_PARM_DESC(debugregs, "expose various EC registers in debugfs (default=false)"); + +static struct dentry *qc71_debugfs_dir, + *qc71_debugfs_regs_dir; + +/* ========================================================================== */ + +static int get_debugfs_byte(void *data, u64 *value) +{ + const struct qc71_debugfs_attr *attr = data; + int status = ec_read_byte(attr->addr); + + if (status < 0) + return status; + + *value = status; + + return 0; +} + +static int set_debugfs_byte(void *data, u64 value) +{ + const struct qc71_debugfs_attr *attr = data; + int status; + + if (value > U8_MAX) + return -EINVAL; + + status = ec_write_byte(attr->addr, (uint8_t) value); + + if (status < 0) + return status; + + return status; +} + +DEFINE_DEBUGFS_ATTRIBUTE(qc71_debugfs_fops, get_debugfs_byte, set_debugfs_byte, "0x%02llx\n"); + +/* ========================================================================== */ + +static ssize_t qc71_debugfs_ec_read(struct file *f, char __user *buf, size_t count, loff_t *offset) +{ + size_t i; + + for (i = 0; *offset + i < U16_MAX && i < count; i++) { + int err = ec_read_byte(*offset + i); + u8 byte; + + if (signal_pending(current)) + return -EINTR; + + if (err < 0) { + if (i) + break; + + return err; + } + + byte = err; + + if (put_user(byte, buf + i)) + return -EFAULT; + } + + *offset += i; + + return i; +} + +static ssize_t qc71_debugfs_ec_write(struct file *f, const char __user *buf, size_t count, loff_t *offset) +{ + size_t i; + + for (i = 0; *offset + i < U16_MAX && i < count; i++) { + int err; + u8 byte; + + if (get_user(byte, buf + i)) + return -EFAULT; + + err = ec_write_byte(*offset + i, byte); + if (err) { + if (i) + break; + + return err; + } + + if (signal_pending(current)) + return -EINTR; + } + + *offset += i; + + return i; +} + +static const struct file_operations qc71_debugfs_ec_fops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = qc71_debugfs_ec_read, + .write = qc71_debugfs_ec_write, + .llseek = default_llseek, +}; + +/* ========================================================================== */ + +int __init qc71_debugfs_setup(void) +{ + struct dentry *d; + int err = 0; + size_t i; + + if (!debugregs) + return -ENODEV; + + qc71_debugfs_dir = debugfs_create_dir(DEBUGFS_DIR_NAME, NULL); + + if (IS_ERR(qc71_debugfs_dir)) { + err = PTR_ERR(qc71_debugfs_dir); + goto out; + } + + qc71_debugfs_regs_dir = debugfs_create_dir("regs", qc71_debugfs_dir); + + if (IS_ERR(qc71_debugfs_regs_dir)) { + err = PTR_ERR(qc71_debugfs_regs_dir); + debugfs_remove_recursive(qc71_debugfs_dir); + goto out; + } + + for (i = 0; i < ARRAY_SIZE(qc71_debugfs_attrs); i++) { + const struct qc71_debugfs_attr *attr = &qc71_debugfs_attrs[i]; + + d = debugfs_create_file(attr->name, 0600, qc71_debugfs_regs_dir, + (void *) attr, &qc71_debugfs_fops); + + if (IS_ERR(d)) { + err = PTR_ERR(d); + debugfs_remove_recursive(qc71_debugfs_dir); + goto out; + } + } + + d = debugfs_create_file("ec", 0600, qc71_debugfs_dir, NULL, &qc71_debugfs_ec_fops); + if (IS_ERR(d)) { + err = PTR_ERR(d); + debugfs_remove_recursive(qc71_debugfs_dir); + goto out; + } + +out: + return err; +} + +void qc71_debugfs_cleanup(void) +{ + /* checks if IS_ERR_OR_NULL() */ + debugfs_remove_recursive(qc71_debugfs_dir); +} + +#endif diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/debugfs.h slimbook-qc71-0.2/qc71_laptop-0.1/debugfs.h --- slimbook-qc71-0.1/qc71_laptop-0.1/debugfs.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/debugfs.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_DEBUGFS_H +#define QC71_DEBUGFS_H + +#if IS_ENABLED(CONFIG_DEBUG_FS) + +#include + +int __init qc71_debugfs_setup(void); +void qc71_debugfs_cleanup(void); + +#else + +static inline int qc71_debugfs_setup(void) +{ + return 0; +} + +static inline void qc71_debugfs_cleanup(void) +{ + +} + +#endif + +#endif /* QC71_DEBUGFS_H */ diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/dkms.conf slimbook-qc71-0.2/qc71_laptop-0.1/dkms.conf --- slimbook-qc71-0.1/qc71_laptop-0.1/dkms.conf 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/dkms.conf 2022-08-18 12:53:42.000000000 +0000 @@ -0,0 +1,7 @@ +MAKE="make KDIR=${kernel_source_dir}" +CLEAN="make KDIR=${kernel_source_dir} clean" +BUILT_MODULE_NAME=qc71_laptop +PACKAGE_NAME=qc71_laptop +PACKAGE_VERSION=0.1 +DEST_MODULE_LOCATION=/kernel/drivers/platform/x86 +AUTOINSTALL=yes diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/ec.c slimbook-qc71-0.2/qc71_laptop-0.1/ec.c --- slimbook-qc71-0.1/qc71_laptop-0.1/ec.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/ec.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include +#include +#include + +#include "ec.h" +#include "wmi.h" + +/* ========================================================================== */ + +static DECLARE_RWSEM(ec_lock); + +/* ========================================================================== */ + +int __must_check qc71_ec_lock(void) +{ + return down_write_killable(&ec_lock); +} + +void qc71_ec_unlock(void) +{ + up_write(&ec_lock); +} + +int __must_check qc71_ec_transaction(uint16_t addr, uint16_t data, + union qc71_ec_result *result, bool read) +{ + uint8_t buf[] = { + addr & 0xFF, + addr >> 8, + data & 0xFF, + data >> 8, + 0, + read ? 1 : 0, + 0, + 0, + }; + static_assert(ARRAY_SIZE(buf) == 8); + + /* the returned ACPI_TYPE_BUFFER is 40 bytes long for some reason ... */ + uint8_t output_buf[sizeof(union acpi_object) + 40]; + + struct acpi_buffer input = { sizeof(buf), buf }, + output = { sizeof(output_buf), output_buf }; + union acpi_object *obj; + acpi_status status = AE_OK; + int err; + + if (read) err = down_read_killable(&ec_lock); + else err = down_write_killable(&ec_lock); + + if (err) + goto out; + + memset(output_buf, 0, sizeof(output_buf)); + + status = wmi_evaluate_method(QC71_WMI_WMBC_GUID, 0, + QC71_WMBC_GETSETULONG_ID, &input, &output); + + if (read) up_read(&ec_lock); + else up_write(&ec_lock); + + if (ACPI_FAILURE(status)) { + err = -EIO; + goto out; + } + + obj = output.pointer; + + if (result) { + if (obj && obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= sizeof(*result)) { + memcpy(result, obj->buffer.pointer, sizeof(*result)); + } else { + err = -ENODATA; + goto out; + } + } + +out: + pr_debug( + "%s(addr=%#06x, data=%#06x, result=%c, read=%c)" + ": (%d) [%#010lx] %s" + ": [%*ph]\n", + + __func__, (unsigned int) addr, (unsigned int) data, + result ? 'y' : 'n', read ? 'y' : 'n', + err, (unsigned long) status, acpi_format_exception(status), + (obj && obj->type == ACPI_TYPE_BUFFER) ? + (int) min(sizeof(*result), (size_t) obj->buffer.length) : 0, + (obj && obj->type == ACPI_TYPE_BUFFER) ? + obj->buffer.pointer : NULL + ); + + return err; +} +ALLOW_ERROR_INJECTION(qc71_ec_transaction, ERRNO); diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/ec.h slimbook-qc71-0.2/qc71_laptop-0.1/ec.h --- slimbook-qc71-0.1/qc71_laptop-0.1/ec.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/ec.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_LAPTOP_EC_H +#define QC71_LAPTOP_EC_H + +#include +#include + +/* ========================================================================== */ +/* + * EC register addresses and bitmasks, + * some of them are not used, + * only for documentation + */ + +#define ADDR(page, offset) (((uint16_t)(page) << 8) | ((uint16_t)(offset))) + +/* ========================================================================== */ + +#define AP_BIOS_BYTE_ADDR ADDR(0x07, 0xA4) +#define AP_BIOS_BYTE_FN_LOCK_SWITCH BIT(3) + +/* ========================================================================== */ + +/* battery charger control register */ +#define BATT_CHARGE_CTRL_ADDR ADDR(0x07, 0xB9) +#define BATT_CHARGE_CTRL_VALUE_MASK GENMASK(6, 0) +#define BATT_CHARGE_CTRL_REACHED BIT(7) + +#define BATT_STATUS_ADDR ADDR(0x04, 0x32) +#define BATT_STATUS_DISCHARGING BIT(0) + +/* possibly temp (in C) = value / 10 + X */ +#define BATT_TEMP_ADDR ADDR(0x04, 0xA2) + +#define BATT_ALERT_ADDR ADDR(0x04, 0x94) + +#define BIOS_CTRL_1_ADDR ADDR(0x07, 0x4E) +#define BIOS_CTRL_1_FN_LOCK_STATUS BIT(4) + +#define BIOS_CTRL_2_ADDR ADDR(0x07, 0x82) +#define BIOS_CTRL_2_FAN_V2_NEW BIT(0) +#define BIOS_CTRL_2_FAN_QKEY BIT(1) +#define BIOS_CTRL_2_OFFICE_MODE_FAN_TABLE_TYPE BIT(2) +#define BIOS_CTRL_2_FAN_V3 BIT(3) +#define BIOS_CTRL_2_DEFAULT_MODE BIT(4) + +/* 3rd control register of a different kind */ +#define BIOS_CTRL_3_ADDR ADDR(0x7, 0xA3) +#define BIOS_CTRL_3_FAN_REDUCED_DUTY_CYCLE BIT(5) +#define BIOS_CTRL_3_FAN_ALWAYS_ON BIT(6) + +#define BIOS_INFO_1_ADDR ADDR(0x04, 0x9F) +#define BIOS_INFO_5_ADDR ADDR(0x04, 0x66) + +/* ========================================================================== */ + +#define CTRL_1_ADDR ADDR(0x07, 0x41) +#define CTRL_1_MANUAL_MODE BIT(0) +#define CTRL_1_ITE_KBD_EFFECT_REACTIVE BIT(3) +#define CTRL_1_FAN_ABNORMAL BIT(5) + +#define CTRL_2_ADDR ADDR(0x07, 0x8C) +#define CTRL_2_SINGLE_COLOR_KEYBOARD BIT(0) +#define CTRL_2_SINGLE_COLOR_KBD_BL_OFF BIT(1) +#define CTRL_2_TURBO_LEVEL_MASK GENMASK(3, 2) +#define CTRL_2_TURBO_LEVEL_0 0x00 +#define CTRL_2_TURBO_LEVEL_1 BIT(2) +#define CTRL_2_TURBO_LEVEL_2 BIT(3) +#define CTRL_2_TURBO_LEVEL_3 (BIT(2) | BIT(3)) +// #define CTRL_2_SINGLE_COLOR_KBD_? BIT(4) +#define CTRL_2_SINGLE_COLOR_KBD_BRIGHTNESS GENMASK(7, 5) + +#define CTRL_3_ADDR ADDR(0x07, 0xA5) +#define CTRL_3_PWR_LED_MASK GENMASK(1, 0) +#define CTRL_3_PWR_LED_NONE BIT(1) +#define CTRL_3_PWR_LED_BOTH BIT(0) +#define CTRL_3_PWR_LED_LEFT 0x00 +#define CTRL_3_FAN_QUIET BIT(2) +#define CTRL_3_OVERBOOST BIT(4) +#define CTRL_3_HIGH_PWR BIT(7) + +#define CTRL_4_ADDR ADDR(0x07, 0xA6) +#define CTRL_4_OVERBOOST_DYN_TEMP_OFF BIT(1) +#define CTRL_4_TOUCHPAD_TOGGLE_OFF BIT(6) + +#define CTRL_5_ADDR ADDR(0x07, 0xC5) + +#define CTRL_6_ADDR ADDR(0x07, 0x8E) + +/* ========================================================================== */ + +#define DEVICE_STATUS_ADDR ADDR(0x04, 0x7B) +#define DEVICE_STATUS_WIFI_ON BIT(7) +/* BIT(5) is seemingly also (un)set depending on the rfkill state (bluetooth?) */ + +/* ========================================================================== */ + +/* fan control register */ +#define FAN_CTRL_ADDR ADDR(0x07, 0x51) +#define FAN_CTRL_LEVEL_MASK GENMASK(2, 0) +#define FAN_CTRL_TURBO BIT(4) +#define FAN_CTRL_AUTO BIT(5) +#define FAN_CTRL_FAN_BOOST BIT(6) +#define FAN_CTRL_SILENT_MODE BIT(7) + +#define FAN_RPM_1_ADDR ADDR(0x04, 0x64) +#define FAN_RPM_2_ADDR ADDR(0x04, 0x6C) + +#define FAN_PWM_1_ADDR ADDR(0x18, 0x04) +#define FAN_PWM_2_ADDR ADDR(0x18, 0x09) + +#define FAN_TEMP_1_ADDR ADDR(0x04, 0x3e) +#define FAN_TEMP_2_ADDR ADDR(0x04, 0x4f) + +#define FAN_MODE_INDEX_ADDR ADDR(0x07, 0xAB) +#define FAN_MODE_INDEX_LOW_MASK GENMASK(3, 0) +#define FAN_MODE_INDEX_HIGH_MASK GENMASK(7, 4) + +/* ========================================================================== */ + +/* + * the actual keyboard type is seemingly determined from this number, + * the project id, the controller firmware version, + * and the HID usage page of the descriptor of the controller + */ +#define KEYBOARD_TYPE_ADDR ADDR(0x07, 0x3C) +#define KEYBOARD_TYPE_101 25 +#define KEYBOARD_TYPE_101M 41 +#define KEYBOARD_TYPE_102 17 +#define KEYBOARD_TYPE_102M 33 +#define KEYBOARD_TYPE_85 25 +#define KEYBOARD_TYPE_86 17 +#define KEYBOARD_TYPE_87 73 +#define KEYBOARD_TYPE_88 65 +#define KEYBOARD_TYPE_97 57 +#define KEYBOARD_TYPE_98 49 +#define KEYBOARD_TYPE_99 121 +#define KEYBOARD_TYPE_100 113 + +/* ========================================================================== */ + +/* lightbar control register */ +#define LIGHTBAR_CTRL_ADDR ADDR(0x07, 0x48) +#define LIGHTBAR_CTRL_POWER_SAVE BIT(1) +#define LIGHTBAR_CTRL_S0_OFF BIT(2) +#define LIGHTBAR_CTRL_S3_OFF BIT(3) +#define LIGHTBAR_CTRL_RAINBOW BIT(7) + +#define LIGHTBAR_RED_ADDR ADDR(0x07, 0x49) +#define LIGHTBAR_GREEN_ADDR ADDR(0x07, 0x4A) +#define LIGHTBAR_BLUE_ADDR ADDR(0x07, 0x4B) + +/* ========================================================================== */ + +#define PROJ_ID_ADDR ADDR(0x07, 0x40) +#define PROJ_ID_GIxKN 1 +#define PROJ_ID_GJxKN 2 +#define PROJ_ID_GKxCN 3 +#define PROJ_ID_GIxCN 4 +#define PROJ_ID_GJxCN 5 +#define PROJ_ID_GK5CN_X 6 +#define PROJ_ID_GK7CN_S 7 +#define PROJ_ID_GK7CPCS_GK5CQ7Z 8 +#define PROJ_ID_PF5NU1G_PF4LUXF 9 +#define PROJ_ID_IDP 11 +#define PROJ_ID_ID6Y 12 +#define PROJ_ID_ID7Y 13 +#define PROJ_ID_PF4MU_PF4MN_PF5MU 14 +#define PROJ_ID_CML_GAMING 15 +#define PROJ_ID_GK7NXXR 16 +#define PROJ_ID_GM5MU1Y 17 + +/* ========================================================================== */ + +#define STATUS_1_ADDR ADDR(0x07, 0x68) +#define STATUS_1_SUPER_KEY_LOCK BIT(0) +#define STATUS_1_LIGHTBAR BIT(1) +#define STATUS_1_FAN_BOOST BIT(2) + +#define SUPPORT_1_ADDR ADDR(0x07, 0x65) +#define SUPPORT_1_AIRPLANE_MODE BIT(0) +#define SUPPORT_1_GPS_SWITCH BIT(1) +#define SUPPORT_1_OVERCLOCK BIT(2) +#define SUPPORT_1_MACRO_KEY BIT(3) +#define SUPPORT_1_SHORTCUT_KEY BIT(4) +#define SUPPORT_1_SUPER_KEY_LOCK BIT(5) +#define SUPPORT_1_LIGHTBAR BIT(6) +#define SUPPORT_1_FAN_BOOST BIT(7) + +#define SUPPORT_2_ADDR ADDR(0x07, 0x66) +#define SUPPORT_2_SILENT_MODE BIT(0) +#define SUPPORT_2_USB_CHARGING BIT(1) +#define SUPPORT_2_SINGLE_ZONE_KBD BIT(2) +#define SUPPORT_2_CHINA_MODE BIT(5) +#define SUPPORT_2_MY_BATTERY BIT(6) + +#define SUPPORT_5_ADDR ADDR(0x07, 0x42) +#define SUPPORT_5_FAN_TURBO BIT(4) +#define SUPPORT_5_FAN BIT(5) + +#define SUPPORT_6 ADDR(0x07, 0x8E) +#define SUPPORT_6_FAN3P5 BIT(1) + +/* ========================================================================== */ + +#define TRIGGER_1_ADDR ADDR(0x07, 0x67) +#define TRIGGER_1_SUPER_KEY_LOCK BIT(0) +#define TRIGGER_1_LIGHTBAR BIT(1) +#define TRIGGER_1_FAN_BOOST BIT(2) +#define TRIGGER_1_SILENT_MODE BIT(3) +#define TRIGGER_1_USB_CHARGING BIT(4) + +#define TRIGGER_2_ADDR ADDR(0x07, 0x5D) + +/* ========================================================================== */ + +#define PLATFORM_ID_ADDR ADDR(0x04, 0x56) +#define POWER_STATUS_ADDR ADDR(0x04, 0x5E) +#define POWER_SOURCE_ADDR ADDR(0x04, 0x90) + +#define PL1_ADDR ADDR(0x07, 0x83) +#define PL2_ADDR ADDR(0x07, 0x84) +#define PL4_ADDR ADDR(0x07, 0x85) + +/* ========================================================================== */ + +union qc71_ec_result { + uint32_t dword; + struct { + uint16_t w1; + uint16_t w2; + } words; + struct { + uint8_t b1; + uint8_t b2; + uint8_t b3; + uint8_t b4; + } bytes; +}; + +int __must_check qc71_ec_transaction(uint16_t addr, uint16_t data, + union qc71_ec_result *result, bool read); + +static inline __must_check int qc71_ec_read(uint16_t addr, union qc71_ec_result *result) +{ + return qc71_ec_transaction(addr, 0, result, true); +} + +static inline __must_check int qc71_ec_write(uint16_t addr, uint16_t data) +{ + return qc71_ec_transaction(addr, data, NULL, false); +} + +static inline __must_check int ec_write_byte(uint16_t addr, uint8_t data) +{ + return qc71_ec_write(addr, data); +} + +static inline __must_check int ec_read_byte(uint16_t addr) +{ + union qc71_ec_result result; + int err; + + err = qc71_ec_read(addr, &result); + + if (err) + return err; + + return result.bytes.b1; +} + +#endif /* QC71_LAPTOP_EC_H */ diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/events.c slimbook-qc71-0.2/qc71_laptop-0.1/events.c --- slimbook-qc71-0.1/qc71_laptop-0.1/events.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/events.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "misc.h" +#include "pdev.h" +#include "wmi.h" + +/* ========================================================================== */ + +#define KBD_BL_LED_SUFFIX ":" LED_FUNCTION_KBD_BACKLIGHT + +/* ========================================================================== */ + +static struct { + const char *guid; + bool handler_installed; +} qc71_wmi_event_guids[] = { + { .guid = QC71_WMI_EVENT0_GUID }, + { .guid = QC71_WMI_EVENT1_GUID }, + { .guid = QC71_WMI_EVENT2_GUID }, +}; + +static const struct key_entry qc71_wmi_hotkeys[] = { + + /* reported via keyboard controller */ + { KE_IGNORE, 0x01, { KEY_CAPSLOCK }}, + { KE_IGNORE, 0x02, { KEY_NUMLOCK }}, + { KE_IGNORE, 0x03, { KEY_SCROLLLOCK }}, + + /* reported via "video bus" */ + { KE_IGNORE, 0x14, { KEY_BRIGHTNESSUP }}, + { KE_IGNORE, 0x15, { KEY_BRIGHTNESSDOWN }}, + + /* reported in automatic mode when rfkill state changes */ + { KE_SW, 0x1a, {.sw = { SW_RFKILL_ALL, 1 }}}, + { KE_SW, 0x1b, {.sw = { SW_RFKILL_ALL, 0 }}}, + + /* reported via keyboard controller */ + { KE_IGNORE, 0x35, { KEY_MUTE }}, + { KE_IGNORE, 0x36, { KEY_VOLUMEDOWN }}, + { KE_IGNORE, 0x37, { KEY_VOLUMEUP }}, + + /* + * not reported by other means when in manual mode, + * handled automatically when it automatic mode + */ + { KE_KEY, 0xa4, { KEY_RFKILL }}, + { KE_KEY, 0xb1, { KEY_KBDILLUMDOWN }}, + { KE_KEY, 0xb2, { KEY_KBDILLUMUP }}, + { KE_KEY, 0xb8, { KEY_FN_ESC }}, + + { KE_END } + +}; + +/* ========================================================================== */ + +static struct input_dev *qc71_input_dev; + +/* ========================================================================== */ + +static void toggle_fn_lock_from_event_handler(void) +{ + int status = qc71_fn_lock_get_state(); + + if (status < 0) + return; + + /* seemingly the returned status in the WMI event handler is not the current */ + pr_info("setting Fn lock state from %d to %d\n", !status, status); + qc71_fn_lock_set_state(status); +} + +#if IS_ENABLED(CONFIG_LEDS_BRIGHTNESS_HW_CHANGED) +extern struct rw_semaphore leds_list_lock; +extern struct list_head leds_list; + +static void emit_keyboard_led_hw_changed(void) +{ + struct led_classdev *led; + + if (down_read_killable(&leds_list_lock)) + return; + + list_for_each_entry (led, &leds_list, node) { + size_t name_length; + const char *suffix; + + if (!(led->flags & LED_BRIGHT_HW_CHANGED)) + continue; + + name_length = strlen(led->name); + + if (name_length < strlen(KBD_BL_LED_SUFFIX)) + continue; + + suffix = led->name + name_length - strlen(KBD_BL_LED_SUFFIX); + + if (strcmp(suffix, KBD_BL_LED_SUFFIX) == 0) { + if (mutex_lock_interruptible(&led->led_access)) + break; + + if (led_update_brightness(led) >= 0) + led_classdev_notify_brightness_hw_changed(led, led->brightness); + + mutex_unlock(&led->led_access); + break; + } + } + + up_read(&leds_list_lock); +} +#endif + +static void qc71_wmi_event_d2_handler(union acpi_object *obj) +{ + bool do_report = true; + + if (!obj || obj->type != ACPI_TYPE_INTEGER) + return; + + switch (obj->integer.value) { + /* caps lock */ + case 1: + pr_info("caps lock\n"); + break; + + /* num lock */ + case 2: + pr_info("num lock\n"); + break; + + /* scroll lock */ + case 3: + pr_info("scroll lock\n"); + break; + + /* touchpad on */ + case 4: + pr_info("touchpad on\n"); + break; + + /* touchpad off */ + case 5: + pr_info("touchpad off\n"); + break; + + /* increase screen brightness */ + case 20: + pr_info("increase screen brightness\n"); + /* do_report = !acpi_video_handles_brightness_key_presses() */ + break; + + /* decrease screen brightness */ + case 21: + pr_info("decrease screen brightness\n"); + /* do_report = !acpi_video_handles_brightness_key_presses() */ + break; + + /* radio on */ + case 26: + /* triggered in automatic mode when the rfkill hotkey is pressed */ + pr_info("radio on\n"); + break; + + /* radio off */ + case 27: + /* triggered in automatic mode when the rfkill hotkey is pressed */ + pr_info("radio off\n"); + break; + + /* mute/unmute */ + case 53: + pr_info("toggle mute\n"); + break; + + /* decrease volume */ + case 54: + pr_info("decrease volume\n"); + break; + + /* increase volume */ + case 55: + pr_info("increase volume\n"); + break; + + case 57: + pr_info("lightbar on\n"); + break; + + case 58: + pr_info("lightbar off\n"); + break; + + /* enable super key (win key) lock */ + case 64: + pr_info("enable super key lock\n"); + break; + + /* decrease volume */ + case 65: + pr_info("disable super key lock\n"); + break; + + /* enable/disable airplane mode */ + case 164: + pr_info("toggle airplane mode\n"); + break; + + /* super key (win key) lock state changed */ + case 165: + pr_info("super key lock state changed\n"); + sysfs_notify(&qc71_platform_dev->dev.kobj, NULL, "super_key_lock"); + break; + + case 166: + pr_info("lightbar state changed\n"); + break; + + /* fan boost state changed */ + case 167: + pr_info("fan boost state changed\n"); + break; + + /* charger unplugged/plugged in */ + case 171: + pr_info("AC plugged/unplugged\n"); + break; + + /* perf mode button pressed */ + case 176: + pr_info("change perf mode\n"); + /* TODO: should it be handled here? */ + break; + + /* increase keyboard backlight */ + case 177: + pr_info("keyboard backlight decrease\n"); + /* TODO: should it be handled here? */ + break; + + /* decrease keyboard backlight */ + case 178: + pr_info("keyboard backlight increase\n"); + /* TODO: should it be handled here? */ + break; + + /* toggle Fn lock (Fn+ESC)*/ + case 184: + pr_info("toggle Fn lock\n"); + toggle_fn_lock_from_event_handler(); + sysfs_notify(&qc71_platform_dev->dev.kobj, NULL, "fn_lock"); + break; + + /* keyboard backlight brightness changed */ + case 240: + pr_info("keyboard backlight changed\n"); + +#if IS_ENABLED(CONFIG_LEDS_BRIGHTNESS_HW_CHANGED) + emit_keyboard_led_hw_changed(); +#endif + break; + + default: + pr_warn("unknown code: %u\n", (unsigned int) obj->integer.value); + break; + } + + if (do_report && qc71_input_dev) + sparse_keymap_report_event(qc71_input_dev, + obj->integer.value, 1, true); + +} + +static void qc71_wmi_event_handler(u32 value, void *context) +{ + struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + + pr_info("%s(value=%#04x)\n", __func__, (unsigned int) value); + status = wmi_get_event_data(value, &response); + + if (ACPI_FAILURE(status)) { + pr_err("bad WMI event status: %#010x\n", (unsigned int) status); + return; + } + + obj = response.pointer; + + if (obj) { + pr_info("obj->type = %d\n", (int) obj->type); + if (obj->type == ACPI_TYPE_INTEGER) { + pr_info("int = %u\n", (unsigned int) obj->integer.value); + } else if (obj->type == ACPI_TYPE_STRING) { + pr_info("string = '%s'\n", obj->string.pointer); + } else if (obj->type == ACPI_TYPE_BUFFER) { + uint32_t i; + + for (i = 0; i < obj->buffer.length; i++) + pr_info("buf[%u] = %#04x\n", + (unsigned int) i, + (unsigned int) obj->buffer.pointer[i]); + } + } + + switch (value) { + case 0xd2: + qc71_wmi_event_d2_handler(obj); + break; + case 0xd1: + case 0xd0: + break; + } + + kfree(obj); +} + +static int __init setup_input_dev(void) +{ + int err = 0; + + qc71_input_dev = input_allocate_device(); + if (!qc71_input_dev) + return -ENOMEM; + + qc71_input_dev->name = "QC71 laptop input device"; + qc71_input_dev->phys = "qc71_laptop/input0"; + qc71_input_dev->id.bustype = BUS_HOST; + qc71_input_dev->dev.parent = &qc71_platform_dev->dev; + + err = sparse_keymap_setup(qc71_input_dev, qc71_wmi_hotkeys, NULL); + if (err) + goto err_free_device; + + err = qc71_rfkill_get_wifi_state(); + if (err >= 0) + input_report_switch(qc71_input_dev, SW_RFKILL_ALL, err); + else + input_report_switch(qc71_input_dev, SW_RFKILL_ALL, 1); + + err = input_register_device(qc71_input_dev); + if (err) + goto err_free_device; + + return err; + +err_free_device: + input_free_device(qc71_input_dev); + qc71_input_dev = NULL; + + return err; +} + +/* ========================================================================== */ + +int __init qc71_wmi_events_setup(void) +{ + int err = 0, i; + + (void) setup_input_dev(); + + for (i = 0; i < ARRAY_SIZE(qc71_wmi_event_guids); i++) { + const char *guid = qc71_wmi_event_guids[i].guid; + acpi_status status = + wmi_install_notify_handler(guid, qc71_wmi_event_handler, NULL); + + if (ACPI_FAILURE(status)) { + pr_warn("could not install WMI notify handler for '%s': [%#010lx] %s\n", + guid, (unsigned long) status, acpi_format_exception(status)); + } else { + qc71_wmi_event_guids[i].handler_installed = true; + } + } + + return err; +} + +void qc71_wmi_events_cleanup(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(qc71_wmi_event_guids); i++) { + if (qc71_wmi_event_guids[i].handler_installed) { + wmi_remove_notify_handler(qc71_wmi_event_guids[i].guid); + qc71_wmi_event_guids[i].handler_installed = false; + } + } + + if (qc71_input_dev) + input_unregister_device(qc71_input_dev); +} diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/events.h slimbook-qc71-0.2/qc71_laptop-0.1/events.h --- slimbook-qc71-0.1/qc71_laptop-0.1/events.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/events.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_WMI_EVENTS_H +#define QC71_WMI_EVENTS_H + +#if IS_ENABLED(CONFIG_LEDS_CLASS) + +#include + +int __init qc71_wmi_events_setup(void); +void qc71_wmi_events_cleanup(void); + +#else + +static inline int qc71_wmi_events_setup(void) +{ + return 0; +} + +static inline void qc71_wmi_events_cleanup(void) +{ + +} + +#endif + +#endif /* QC71_WMI_EVENTS_H */ diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/fan.c slimbook-qc71-0.2/qc71_laptop-0.1/fan.c --- slimbook-qc71-0.1/qc71_laptop-0.1/fan.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/fan.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0) +static inline int fixp_linear_interpolate(int x0, int y0, int x1, int y1, int x) +{ + if (y0 == y1 || x == x0) + return y0; + if (x1 == x0 || x == x1) + return y1; + + return y0 + ((y1 - y0) * (x - x0) / (x1 - x0)); +} +#else +#include /* fixp-arith.h needs it, but doesn't include it */ +#include +#endif + +#include +#include +#include + +#include "ec.h" +#include "fan.h" +#include "util.h" + +/* ========================================================================== */ + +static const uint16_t qc71_fan_rpm_addrs[] = { + FAN_RPM_1_ADDR, + FAN_RPM_2_ADDR, +}; + +static const uint16_t qc71_fan_pwm_addrs[] = { + FAN_PWM_1_ADDR, + FAN_PWM_2_ADDR, +}; + +static const uint16_t qc71_fan_temp_addrs[] = { + FAN_TEMP_1_ADDR, + FAN_TEMP_2_ADDR, +}; + +/* ========================================================================== */ + +static DEFINE_MUTEX(fan_lock); + +/* ========================================================================== */ + +static int qc71_fan_get_status(void) +{ + return ec_read_byte(FAN_CTRL_ADDR); +} + +/* 'fan_lock' must be held */ +static int qc71_fan_get_mode_unlocked(void) +{ + int err; + + lockdep_assert_held(&fan_lock); + + err = ec_read_byte(CTRL_1_ADDR); + if (err < 0) + return err; + + if (err & CTRL_1_MANUAL_MODE) { + err = qc71_fan_get_status(); + if (err < 0) + return err; + + if (err & FAN_CTRL_FAN_BOOST) { + err = qc71_fan_get_pwm(0); + + if (err < 0) + return err; + + if (err == FAN_MAX_PWM) + err = 0; /* disengaged */ + else + err = 1; /* manual */ + + } else if (err & FAN_CTRL_AUTO) { + err = 2; /* automatic fan control */ + } else { + err = 1; /* manual */ + } + } else { + err = 2; /* automatic fan control */ + } + + return err; +} + +/* ========================================================================== */ + +int qc71_fan_get_rpm(uint8_t fan_index) +{ + union qc71_ec_result res; + int err; + + if (fan_index >= ARRAY_SIZE(qc71_fan_rpm_addrs)) + return -EINVAL; + + err = qc71_ec_read(qc71_fan_rpm_addrs[fan_index], &res); + + if (err) + return err; + + return res.bytes.b1 << 8 | res.bytes.b2; +} + +int qc71_fan_query_abnorm(void) +{ + int res = ec_read_byte(CTRL_1_ADDR); + + if (res < 0) + return res; + + return !!(res & CTRL_1_FAN_ABNORMAL); +} + +int qc71_fan_get_pwm(uint8_t fan_index) +{ + int err; + + if (fan_index >= ARRAY_SIZE(qc71_fan_pwm_addrs)) + return -EINVAL; + + err = ec_read_byte(qc71_fan_pwm_addrs[fan_index]); + if (err < 0) + return err; + + return fixp_linear_interpolate(0, 0, FAN_MAX_PWM, U8_MAX, err); +} + +int qc71_fan_set_pwm(uint8_t fan_index, uint8_t pwm) +{ + if (fan_index >= ARRAY_SIZE(qc71_fan_pwm_addrs)) + return -EINVAL; + + return ec_write_byte(qc71_fan_pwm_addrs[fan_index], + fixp_linear_interpolate(0, 0, + U8_MAX, FAN_MAX_PWM, + pwm)); +} + +int qc71_fan_get_temp(uint8_t fan_index) +{ + if (fan_index >= ARRAY_SIZE(qc71_fan_temp_addrs)) + return -EINVAL; + + return ec_read_byte(qc71_fan_temp_addrs[fan_index]); +} + +int qc71_fan_get_mode(void) +{ + int err = mutex_lock_interruptible(&fan_lock); + + if (err) + return err; + + err = qc71_fan_get_mode_unlocked(); + + mutex_unlock(&fan_lock); + return err; +} + +int qc71_fan_set_mode(uint8_t mode) +{ + int err, oldpwm; + + err = mutex_lock_interruptible(&fan_lock); + if (err) + return err; + + switch (mode) { + case 0: + err = ec_write_byte(FAN_CTRL_ADDR, FAN_CTRL_FAN_BOOST); + if (err) + goto out; + + err = qc71_fan_set_pwm(0, FAN_MAX_PWM); + break; + case 1: + oldpwm = err = qc71_fan_get_pwm(0); + if (err < 0) + goto out; + + err = ec_write_byte(FAN_CTRL_ADDR, FAN_CTRL_FAN_BOOST); + if (err < 0) + goto out; + + err = qc71_fan_set_pwm(0, oldpwm); + if (err < 0) + (void) ec_write_byte(FAN_CTRL_ADDR, 0x80 | FAN_CTRL_AUTO); + /* try to restore automatic fan control */ + + break; + case 2: + err = ec_write_byte(FAN_CTRL_ADDR, 0x80 | FAN_CTRL_AUTO); + break; + default: + err = -EINVAL; + break; + } + +out: + mutex_unlock(&fan_lock); + return err; +} diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/fan.h slimbook-qc71-0.2/qc71_laptop-0.1/fan.h --- slimbook-qc71-0.1/qc71_laptop-0.1/fan.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/fan.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,22 @@ +#ifndef QC71_LAPTOP_FAN_H +#define QC71_LAPTOP_FAN_H + +#include + +/* ========================================================================== */ + +#define FAN_MAX_PWM 200 +#define FAN_CTRL_MAX_LEVEL 7 +#define FAN_CTRL_LEVEL(level) (128 + (level)) + +/* ========================================================================== */ + +int qc71_fan_get_rpm(uint8_t fan_index); +int qc71_fan_query_abnorm(void); +int qc71_fan_get_pwm(uint8_t fan_index); +int qc71_fan_set_pwm(uint8_t fan_index, uint8_t pwm); +int qc71_fan_get_temp(uint8_t fan_index); +int qc71_fan_get_mode(void); +int qc71_fan_set_mode(uint8_t mode); + +#endif /* QC71_LAPTOP_FAN_H */ diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/features.c slimbook-qc71-0.2/qc71_laptop-0.1/features.c --- slimbook-qc71-0.1/qc71_laptop-0.1/features.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/features.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include +#include +#include + +#include "ec.h" +#include "features.h" + +/* ========================================================================== */ + +struct oem_string_walker_data { + char *value; + int index; +}; + +/* ========================================================================== */ + +static int __init slimbook_dmi_cb(const struct dmi_system_id *id) +{ + qc71_features.fn_lock = true; + qc71_features.silent_mode = true; + + return 1; +} + +static const struct dmi_system_id qc71_dmi_table[] __initconst = { + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "LAPQC71"), + { } + } + }, + { + /* https://avell.com.br/avell-a60-muv-295765 */ + .matches = { + DMI_EXACT_MATCH(DMI_CHASSIS_VENDOR, "Avell High Performance"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "A60 MUV"), + { } + } + }, + { + /* Slimbook PROX AMD */ + .callback = slimbook_dmi_cb, + .matches = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "PROX-AMD"), + { } + } + }, + { + /* Slimbook PROX15 AMD */ + .callback = slimbook_dmi_cb, + .matches = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "PROX15-AMD"), + { } + } + }, + { + /* Slimbook PROX AMD5 */ + .callback = slimbook_dmi_cb, + .matches = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME,"PROX-AMD5"), + { } + } + }, + { + /* Slimbook PROX15 AMD5 */ + .callback = slimbook_dmi_cb, + .matches = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME,"PROX15-AMD5"), + { } + } + }, + { } +}; + +/* ========================================================================== */ + +struct qc71_features_struct qc71_features; + +/* ========================================================================== */ + +static void __init oem_string_walker(const struct dmi_header *dm, void *ptr) +{ + int i, count; + const uint8_t *s; + struct oem_string_walker_data *data = ptr; + + if (dm->type != 11 || dm->length < 5 || !IS_ERR_OR_NULL(data->value)) + return; + + count = *(uint8_t *)(dm + 1); + + if (data->index >= count) + return; + + i = 0; + s = ((uint8_t *)dm) + dm->length; + + while (i++ < data->index && *s) + s += strlen(s) + 1; + + data->value = kstrdup(s, GFP_KERNEL); + + if (!data->value) + data->value = ERR_PTR(-ENOMEM); +} + +static char * __init read_oem_string(int index) +{ + struct oem_string_walker_data d = {.value = ERR_PTR(-ENOENT), + .index = index}; + int err = dmi_walk(oem_string_walker, &d); + + if (err) { + if (!IS_ERR_OR_NULL(d.value)) + kfree(d.value); + return ERR_PTR(err); + } + + return d.value; +} + +/* QCCFL357.0062.2020.0313.1530 -> 62 */ +static int __pure __init parse_bios_version(const char *str) +{ + const char *p = strchr(str, '.'), *p2; + int bios_version; + + if (!p) + return -EINVAL; + + p2 = strchr(p + 1, '.'); + + if (!p2) + return -EINVAL; + + p += 1; + + bios_version = 0; + + while (p != p2) { + if (!isdigit(*p)) + return -EINVAL; + + bios_version = 10 * bios_version + *p - '0'; + p += 1; + } + + return bios_version; +} + +static int __init check_features_ec(void) +{ + int err = ec_read_byte(SUPPORT_1_ADDR); + + if (err >= 0) { + qc71_features.super_key_lock = !!(err & SUPPORT_1_SUPER_KEY_LOCK); + qc71_features.lightbar = !!(err & SUPPORT_1_LIGHTBAR); + qc71_features.fan_boost = !!(err & SUPPORT_1_FAN_BOOST); + } else { + pr_warn("failed to query support_1 byte: %d\n", err); + } + + return err; +} + +static int __init check_features_bios(void) +{ + const char *bios_version_str; + int bios_version; + + if (!dmi_check_system(qc71_dmi_table)) { + pr_warn("no DMI match\n"); + return -ENODEV; + } + + bios_version_str = dmi_get_system_info(DMI_BIOS_VERSION); + + if (!bios_version_str) { + pr_warn("failed to get BIOS version DMI string\n"); + return -ENOENT; + } + + pr_info("BIOS version string: '%s'\n", bios_version_str); + + bios_version = parse_bios_version(bios_version_str); + + if (bios_version < 0) { + pr_warn("cannot parse BIOS version\n"); + return -EINVAL; + } + + pr_info("BIOS version: %04d\n", bios_version); + + if (bios_version >= 114) { + const char *s = read_oem_string(18); + size_t s_len; + + if (IS_ERR(s)) + return PTR_ERR(s); + + s_len = strlen(s); + + pr_info("OEM_STRING(18) = '%s'\n", s); + + /* if it is entirely spaces */ + if (strspn(s, " ") == s_len) { + qc71_features.fn_lock = true; + qc71_features.batt_charge_limit = true; + qc71_features.fan_extras = true; + } else if (s_len > 0) { + /* TODO */ + pr_warn("cannot extract supported features"); + } + + kfree(s); + } + + return 0; +} + +int __init qc71_check_features(void) +{ + (void) check_features_ec(); + (void) check_features_bios(); + + return 0; +} diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/features.h slimbook-qc71-0.2/qc71_laptop-0.1/features.h --- slimbook-qc71-0.1/qc71_laptop-0.1/features.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/features.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_FEATURES_H +#define QC71_FEATURES_H + +#include +#include + +struct qc71_features_struct { + bool super_key_lock : 1; + bool lightbar : 1; + bool fan_boost : 1; + bool fn_lock : 1; + bool batt_charge_limit : 1; + bool fan_extras : 1; /* duty cycle reduction, always on mode */ + bool silent_mode : 1; /* Slimbook silent mode: decreases fan rpm limit and tdp */ +}; + +/* ========================================================================== */ + +extern struct qc71_features_struct qc71_features; + +/* ========================================================================== */ + +int __init qc71_check_features(void); + +#endif /* QC71_FEATURES_H */ diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/hwmon.c slimbook-qc71-0.2/qc71_laptop-0.1/hwmon.c --- slimbook-qc71-0.1/qc71_laptop-0.1/hwmon.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/hwmon.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include + +#include "hwmon_fan.h" +#include "hwmon_pwm.h" + +/* ========================================================================== */ + +static bool nohwmon; +module_param(nohwmon, bool, 0444); +MODULE_PARM_DESC(nohwmon, "do not report to the hardware monitoring subsystem (default=false)"); + +/* ========================================================================== */ + +int __init qc71_hwmon_setup(void) +{ + if (nohwmon) + return -ENODEV; + + (void) qc71_hwmon_fan_setup(); + (void) qc71_hwmon_pwm_setup(); + + return 0; +} + +void qc71_hwmon_cleanup(void) +{ + (void) qc71_hwmon_fan_cleanup(); + (void) qc71_hwmon_pwm_cleanup(); +} diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/hwmon_fan.c slimbook-qc71-0.2/qc71_laptop-0.1/hwmon_fan.c --- slimbook-qc71-0.1/qc71_laptop-0.1/hwmon_fan.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/hwmon_fan.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include +#include +#include + +#include "ec.h" +#include "fan.h" +#include "features.h" +#include "pdev.h" + +/* ========================================================================== */ + +static struct device *qc71_hwmon_fan_dev; + +/* ========================================================================== */ + +static umode_t qc71_hwmon_fan_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + case hwmon_fan_fault: + return 0444; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_label: + return 0444; + } + default: + break; + } + + return 0; +} + +static int qc71_hwmon_fan_read(struct device *device, enum hwmon_sensor_types type, + u32 attr, int channel, long *value) +{ + int err; + + switch (type) { + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + err = qc71_fan_get_rpm(channel); + if (err < 0) + return err; + + *value = err; + break; + default: + return -EOPNOTSUPP; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + err = qc71_fan_get_temp(channel); + if (err < 0) + return err; + + *value = err * 1000; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int qc71_hwmon_fan_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + static const char * const temp_labels[] = { + "fan1_temp", + "fan2_temp", + }; + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_label: + *str = temp_labels[channel]; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +/* ========================================================================== */ + +static const struct hwmon_channel_info *qc71_hwmon_fan_ch_info[] = { + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT, + HWMON_F_INPUT), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + NULL +}; + +static const struct hwmon_ops qc71_hwmon_fan_ops = { + .is_visible = qc71_hwmon_fan_is_visible, + .read = qc71_hwmon_fan_read, + .read_string = qc71_hwmon_fan_read_string, +}; + +static const struct hwmon_chip_info qc71_hwmon_fan_chip_info = { + .ops = &qc71_hwmon_fan_ops, + .info = qc71_hwmon_fan_ch_info, +}; + +/* ========================================================================== */ + +int __init qc71_hwmon_fan_setup(void) +{ + int err = 0; + + qc71_hwmon_fan_dev = hwmon_device_register_with_info( + &qc71_platform_dev->dev, KBUILD_MODNAME ".hwmon.fan", NULL, + &qc71_hwmon_fan_chip_info, NULL); + + if (IS_ERR(qc71_hwmon_fan_dev)) + err = PTR_ERR(qc71_hwmon_fan_dev); + + return err; +} + +void qc71_hwmon_fan_cleanup(void) +{ + if (!IS_ERR_OR_NULL(qc71_hwmon_fan_dev)) + hwmon_device_unregister(qc71_hwmon_fan_dev); +} diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/hwmon_fan.h slimbook-qc71-0.2/qc71_laptop-0.1/hwmon_fan.h --- slimbook-qc71-0.1/qc71_laptop-0.1/hwmon_fan.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/hwmon_fan.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_HWMON_FAN_H +#define QC71_HWMON_FAN_H + +#include + +int __init qc71_hwmon_fan_setup(void); +void qc71_hwmon_fan_cleanup(void); + +#endif /* QC71_HWMON_FAN_H */ diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/hwmon.h slimbook-qc71-0.2/qc71_laptop-0.1/hwmon.h --- slimbook-qc71-0.1/qc71_laptop-0.1/hwmon.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/hwmon.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_HWMON_H +#define QC71_HWMON_H + +#if IS_ENABLED(CONFIG_HWMON) + +#include + +int __init qc71_hwmon_setup(void); +void qc71_hwmon_cleanup(void); + +#else + +static inline int qc71_hwmon_setup(void) +{ + return 0; +} + +static inline void qc71_hwmon_cleanup(void) +{ + +} + +#endif + +#endif /* QC71_HWMON_H */ diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/hwmon_pwm.c slimbook-qc71-0.2/qc71_laptop-0.1/hwmon_pwm.c --- slimbook-qc71-0.1/qc71_laptop-0.1/hwmon_pwm.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/hwmon_pwm.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fan.h" +#include "features.h" +#include "pdev.h" +#include "util.h" + +/* ========================================================================== */ + +static struct device *qc71_hwmon_pwm_dev; + +/* ========================================================================== */ + +static umode_t qc71_hwmon_pwm_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type != hwmon_pwm && attr != hwmon_pwm_enable) + return -EOPNOTSUPP; + + return 0644; +} + +static int qc71_hwmon_pwm_read(struct device *device, enum hwmon_sensor_types type, + u32 attr, int channel, long *value) +{ + int err; + + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_enable: + err = qc71_fan_get_mode(); + if (err < 0) + return err; + + *value = err; + break; + case hwmon_pwm_input: + err = qc71_fan_get_pwm(channel); + if (err < 0) + return err; + + *value = err; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int qc71_hwmon_pwm_write(struct device *device, enum hwmon_sensor_types type, + u32 attr, int channel, long value) +{ + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_enable: + return qc71_fan_set_mode(value); + case hwmon_pwm_input: + return qc71_fan_set_pwm(channel, value); + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static const struct hwmon_channel_info *qc71_hwmon_pwm_ch_info[] = { + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE), + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT, HWMON_PWM_INPUT), + NULL +}; + +static const struct hwmon_ops qc71_hwmon_pwm_ops = { + .is_visible = qc71_hwmon_pwm_is_visible, + .read = qc71_hwmon_pwm_read, + .write = qc71_hwmon_pwm_write, +}; + +static const struct hwmon_chip_info qc71_hwmon_pwm_chip_info = { + .ops = &qc71_hwmon_pwm_ops, + .info = qc71_hwmon_pwm_ch_info, +}; + +/* ========================================================================== */ + +int __init qc71_hwmon_pwm_setup(void) +{ + int err = 0; + + if (!qc71_features.fan_boost) + return -ENODEV; + + qc71_hwmon_pwm_dev = hwmon_device_register_with_info( + &qc71_platform_dev->dev, KBUILD_MODNAME ".hwmon.pwm", NULL, + &qc71_hwmon_pwm_chip_info, NULL); + + if (IS_ERR(qc71_hwmon_pwm_dev)) + err = PTR_ERR(qc71_hwmon_pwm_dev); + + return err; +} + +void qc71_hwmon_pwm_cleanup(void) +{ + if (!IS_ERR_OR_NULL(qc71_hwmon_pwm_dev)) + hwmon_device_unregister(qc71_hwmon_pwm_dev); +} diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/hwmon_pwm.h slimbook-qc71-0.2/qc71_laptop-0.1/hwmon_pwm.h --- slimbook-qc71-0.1/qc71_laptop-0.1/hwmon_pwm.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/hwmon_pwm.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_HWMON_PWM_H +#define QC71_HWMON_PWM_H + +#include + +int __init qc71_hwmon_pwm_setup(void); +void qc71_hwmon_pwm_cleanup(void); + +#endif /* QC71_HWMON_PWM_H */ diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/led_lightbar.c slimbook-qc71-0.2/qc71_laptop-0.1/led_lightbar.c --- slimbook-qc71-0.1/qc71_laptop-0.1/led_lightbar.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/led_lightbar.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +/* #include */ +#include +#include +#include + +#include "util.h" +#include "ec.h" +#include "features.h" +#include "led_lightbar.h" +#include "pdev.h" + +/* ========================================================================== */ + +#if IS_ENABLED(CONFIG_LEDS_CLASS) + +enum qc71_lightbar_color { + LIGHTBAR_RED = 0, + LIGHTBAR_GREEN = 1, + LIGHTBAR_BLUE = 2, + LIGHTBAR_COLOR_COUNT +}; + +static const uint16_t lightbar_color_addrs[LIGHTBAR_COLOR_COUNT] = { + [LIGHTBAR_RED] = LIGHTBAR_RED_ADDR, + [LIGHTBAR_GREEN] = LIGHTBAR_GREEN_ADDR, + [LIGHTBAR_BLUE] = LIGHTBAR_BLUE_ADDR, +}; + +static const uint8_t lightbar_colors[LIGHTBAR_COLOR_COUNT] = { + LIGHTBAR_RED, + LIGHTBAR_GREEN, + LIGHTBAR_BLUE, +}; + +/* f(x) = 4x */ +static const uint8_t lightbar_color_values[LIGHTBAR_COLOR_COUNT][10] = { + [LIGHTBAR_RED] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36}, + [LIGHTBAR_GREEN] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36}, + [LIGHTBAR_BLUE] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36}, +}; + +/* inverse of 'lightbar_color_values' */ +static const uint8_t lightbar_pwm_to_level[LIGHTBAR_COLOR_COUNT][256] = { + [LIGHTBAR_RED] = { + [0] = 0, + [4] = 1, + [8] = 2, + [12] = 3, + [16] = 4, + [20] = 5, + [24] = 6, + [28] = 7, + [32] = 8, + [36] = 9, + }, + + [LIGHTBAR_GREEN] = { + [0] = 0, + [4] = 1, + [8] = 2, + [12] = 3, + [16] = 4, + [20] = 5, + [24] = 6, + [28] = 7, + [32] = 8, + [36] = 9, + }, + + [LIGHTBAR_BLUE] = { + [0] = 0, + [4] = 1, + [8] = 2, + [12] = 3, + [16] = 4, + [20] = 5, + [24] = 6, + [28] = 7, + [32] = 8, + [36] = 9, + }, +}; + + +/* ========================================================================== */ + +static bool nolightbar; +module_param(nolightbar, bool, 0444); +MODULE_PARM_DESC(nolightbar, "do not register the lightbar to the leds subsystem (default=false)"); + +static bool lightbar_led_registered; + +/* ========================================================================== */ + +static inline int qc71_lightbar_get_status(void) +{ + return ec_read_byte(LIGHTBAR_CTRL_ADDR); +} + +static inline int qc71_lightbar_write_ctrl(uint8_t ctrl) +{ + return ec_write_byte(LIGHTBAR_CTRL_ADDR, ctrl); +} + +/* ========================================================================== */ + +static int qc71_lightbar_switch(uint8_t mask, bool on) +{ + int status; + + if (mask != LIGHTBAR_CTRL_S0_OFF && mask != LIGHTBAR_CTRL_S3_OFF) + return -EINVAL; + + status = qc71_lightbar_get_status(); + + if (status < 0) + return status; + + return qc71_lightbar_write_ctrl(SET_BIT(status, mask, !on)); +} + +static int qc71_lightbar_set_color_level(uint8_t color, uint8_t level) +{ + if (color >= ARRAY_SIZE(lightbar_color_addrs)) + return -EINVAL; + + if (level >= ARRAY_SIZE(lightbar_color_values[color])) + return -EINVAL; + + return ec_write_byte(lightbar_color_addrs[color], lightbar_color_values[color][level]); +} + +static int qc71_lightbar_get_color_level(uint8_t color) +{ + int err; + + if (color >= ARRAY_SIZE(lightbar_color_addrs)) + return -EINVAL; + + err = ec_read_byte(lightbar_color_addrs[color]); + if (err < 0) + return err; + + return lightbar_pwm_to_level[color][err]; +} + +static int qc71_lightbar_set_rainbow_mode(bool on) +{ + int status = qc71_lightbar_get_status(); + + if (status < 0) + return status; + + return qc71_lightbar_write_ctrl(SET_BIT(status, LIGHTBAR_CTRL_RAINBOW, on)); +} + +static int qc71_lightbar_set_color(unsigned int color) +{ + int err = 0, i; + + if (color > 999) /* color must lie in [0, 999] */ + return -EINVAL; + + for (i = ARRAY_SIZE(lightbar_colors) - 1; i >= 0 && !err; i--) + err = qc71_lightbar_set_color_level(lightbar_colors[i], + do_div(color, 10)); + + return err; +} + +/* ========================================================================== */ +/* lightbar attrs */ + +static ssize_t lightbar_s3_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int value = qc71_lightbar_get_status(); + + if (value < 0) + return value; + + return sprintf(buf, "%d\n", !(value & LIGHTBAR_CTRL_S3_OFF)); +} + +static ssize_t lightbar_s3_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + err = qc71_lightbar_switch(LIGHTBAR_CTRL_S3_OFF, value); + + if (err) + return err; + + return count; +} + +static ssize_t lightbar_color_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int color = 0; + size_t i; + + for (i = 0; i < ARRAY_SIZE(lightbar_colors); i++) { + int level = qc71_lightbar_get_color_level(lightbar_colors[i]); + + if (level < 0) + return level; + + color *= 10; + + if (0 <= level && level <= 9) + color += level; + } + + return sprintf(buf, "%03u\n", color); +} + +static ssize_t lightbar_color_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int value; + int err; + + if (kstrtouint(buf, 10, &value)) + return -EINVAL; + + err = qc71_lightbar_set_color(value); + if (err) + return err; + + return count; +} + +static ssize_t lightbar_rainbow_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = qc71_lightbar_get_status(); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", !!(status & LIGHTBAR_CTRL_RAINBOW)); +} + +static ssize_t lightbar_rainbow_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + err = qc71_lightbar_set_rainbow_mode(value); + + if (err) + return err; + + return count; +} + +static enum led_brightness qc71_lightbar_led_get_brightness(struct led_classdev *led_cdev) +{ + int err = qc71_lightbar_get_status(); + + if (err) + return 0; + + return !(err & LIGHTBAR_CTRL_S0_OFF); +} + +static int qc71_lightbar_led_set_brightness(struct led_classdev *led_cdev, + enum led_brightness value) +{ + return qc71_lightbar_switch(LIGHTBAR_CTRL_S0_OFF, !!value); +} + +#if 0 +static int qc71_lightbar_led_set_brightness(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); + unsigned int color = 0, i; + int err; + + led_mc_calc_color_components(led_mc_cdev, brightness); + + for (i = 0; i < led_mc_cdev->num_colors; i++) { + if (led_mc_cdev->subled_info[i].brightness > 9) + return -EINVAL; + + color = 10 * color + led_mc_cdev->subled_info[i].brightness; + } + + if (color) { + err = qc71_lightbar_switch(LIGHTBAR_CTRL_S0_OFF, 1); + + if (err) + goto out; + + err = qc71_lightbar_set_color(color); + } else { + err = qc71_lightbar_switch(LIGHTBAR_CTRL_S0_OFF, 0); + } + +out: + return err; +} +#endif + +/* ========================================================================== */ + +static DEVICE_ATTR(brightness_s3, 0644, lightbar_s3_show, lightbar_s3_store); +static DEVICE_ATTR(color, 0644, lightbar_color_show, lightbar_color_store); +static DEVICE_ATTR(rainbow_mode, 0644, lightbar_rainbow_show, lightbar_rainbow_store); + +static struct attribute *qc71_lightbar_led_attrs[] = { + &dev_attr_brightness_s3.attr, + &dev_attr_color.attr, + &dev_attr_rainbow_mode.attr, + NULL +}; + +ATTRIBUTE_GROUPS(qc71_lightbar_led); + +static struct led_classdev qc71_lightbar_led = { + .name = KBUILD_MODNAME "::lightbar", + .max_brightness = 1, + .brightness_get = qc71_lightbar_led_get_brightness, + .brightness_set_blocking = qc71_lightbar_led_set_brightness, + .groups = qc71_lightbar_led_groups, +}; + +#if 0 +static struct mc_subled qc71_lightbar_subleds[LIGHTBAR_COLOR_COUNT] = { + [LIGHTBAR_RED] = { + .color_index = LED_COLOR_ID_RED, + }, + [LIGHTBAR_GREEN] = { + .color_index = LED_COLOR_ID_GREEN, + }, + [LIGHTBAR_BLUE] = { + .color_index = LED_COLOR_ID_BLUE, + }, +}; + +static struct led_classdev_mc qc71_lightbar_led = { + .num_colors = ARRAY_SIZE(qc71_lightbar_subleds), + .subled_info = qc71_lightbar_subleds, + .led_cdev = { + .name = KBUILD_MODNAME "::lightbar", + .max_brightness = 9, + .brightness_set_blocking = qc71_lightbar_led_set_brightness, + }, +}; +#endif + +/* ========================================================================== */ + +int __init qc71_led_lightbar_setup(void) +{ + int err; + + if (nolightbar || !qc71_features.lightbar) + return -ENODEV; + +#if 0 + err = led_classdev_multicolor_register(&qc71_platform_dev->dev, &qc71_lightbar_led); +#endif + err = led_classdev_register(&qc71_platform_dev->dev, &qc71_lightbar_led); + + if (!err) + lightbar_led_registered = true; + +#if 0 + err = device_add_groups(qc71_lightbar_led.led_cdev.dev, qc71_lightbar_led_groups); + if (err) + led_classdev_multicolor_unregister(&qc71_lightbar_led); +#endif + + return err; +} + +void qc71_led_lightbar_cleanup(void) +{ + if (lightbar_led_registered) { +#if 0 + device_remove_groups(qc71_lightbar_led.led_cdev.dev, qc71_lightbar_led_groups); + led_classdev_multicolor_unregister(&qc71_lightbar_led); +#endif + led_classdev_unregister(&qc71_lightbar_led); + } +} + +#endif diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/led_lightbar.h slimbook-qc71-0.2/qc71_laptop-0.1/led_lightbar.h --- slimbook-qc71-0.1/qc71_laptop-0.1/led_lightbar.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/led_lightbar.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_LED_LIGHTBAR_H +#define QC71_LED_LIGHTBAR_H + +#if IS_ENABLED(CONFIG_LEDS_CLASS) + +#include + +int __init qc71_led_lightbar_setup(void); +void qc71_led_lightbar_cleanup(void); + +#else + +static inline int qc71_led_lightbar_setup(void) +{ + return 0; +} + +static inline void qc71_led_lightbar_cleanup(void) +{ + +} + +#endif + +#endif /* QC71_LED_LIGHTBAR_H */ diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/LICENSE slimbook-qc71-0.2/qc71_laptop-0.1/LICENSE --- slimbook-qc71-0.1/qc71_laptop-0.1/LICENSE 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/LICENSE 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/main.c slimbook-qc71-0.2/qc71_laptop-0.1/main.c --- slimbook-qc71-0.1/qc71_laptop-0.1/main.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/main.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0 +/* ========================================================================== */ +/* https://www.intel.com/content/dam/support/us/en/documents/laptops/whitebook/QC71_PROD_SPEC.pdf + * + * + * based on the following resources: + * - https://lwn.net/Articles/391230/ + * - http://blog.nietrzeba.pl/2011/12/mof-decompilation.html + * - https://github.com/tuxedocomputers/tuxedo-cc-wmi/ + * - https://github.com/tuxedocomputers/tuxedo-keyboard/ + * - Control Center for Microsoft Windows + * - http://forum.notebookreview.com/threads/tongfang-gk7cn6s-gk7cp0s-gk7cp7s.825461/page-54 + */ +/* ========================================================================== */ +#include "pr.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "ec.h" +#include "features.h" +#include "wmi.h" + +/* submodules */ +#include "pdev.h" +#include "events.h" +#include "hwmon.h" +#include "battery.h" +#include "led_lightbar.h" +#include "debugfs.h" + +/* ========================================================================== */ + +#define SUBMODULE_ENTRY(_name, _req) { .name = #_name, .init = qc71_ ## _name ## _setup, .cleanup = qc71_ ## _name ## _cleanup, .required = _req } + +static struct qc71_submodule { + const char *name; + + bool required : 1, + initialized : 1; + + int (*init)(void); + void (*cleanup)(void); +} qc71_submodules[] __refdata = { + SUBMODULE_ENTRY(pdev, true), /* must be first */ + SUBMODULE_ENTRY(wmi_events, false), + SUBMODULE_ENTRY(hwmon, false), + SUBMODULE_ENTRY(battery, false), + SUBMODULE_ENTRY(led_lightbar, false), + SUBMODULE_ENTRY(debugfs, false), +}; + +#undef SUBMODULE_ENTRY + +static void do_cleanup(void) +{ + int i; + + for (i = ARRAY_SIZE(qc71_submodules) - 1; i >= 0; i--) { + const struct qc71_submodule *sm = &qc71_submodules[i]; + + if (sm->initialized) + sm->cleanup(); + } +} + +static int __init qc71_laptop_module_init(void) +{ + int err = 0, i; + + if (!wmi_has_guid(QC71_WMI_WMBC_GUID)) { + pr_err("WMI GUID not found\n"); + err = -ENODEV; goto out; + } + + err = ec_read_byte(PROJ_ID_ADDR); + if (err < 0) { + pr_err("failed to query project id: %d\n", err); + goto out; + } + + pr_info("project id: %d\n", err); + + err = ec_read_byte(PLATFORM_ID_ADDR); + if (err < 0) { + pr_err("failed to query platform id: %d\n", err); + goto out; + } + + pr_info("platform id: %d\n", err); + + err = qc71_check_features(); + if (err) { + pr_err("cannot check system features: %d\n", err); + goto out; + } + + pr_info("supported features:"); + if (qc71_features.super_key_lock) pr_cont(" super-key-lock"); + if (qc71_features.lightbar) pr_cont(" lightbar"); + if (qc71_features.fan_boost) pr_cont(" fan-boost"); + if (qc71_features.fn_lock) pr_cont(" fn-lock"); + if (qc71_features.batt_charge_limit) pr_cont(" charge-limit"); + if (qc71_features.fan_extras) pr_cont(" fan-extras"); + if (qc71_features.silent_mode) pr_cont(" silent-mode"); + + pr_cont("\n"); + + for (i = 0; i < ARRAY_SIZE(qc71_submodules); i++) { + struct qc71_submodule *sm = &qc71_submodules[i]; + + err = sm->init(); + if (err) { + pr_warn("failed to initialize %s submodule: %d\n", sm->name, err); + if (sm->required) + goto out; + } else { + sm->initialized = true; + } + } + + err = 0; + +out: + if (err) + do_cleanup(); + else + pr_info("module loaded\n"); + + return err; +} + +static void __exit qc71_laptop_module_cleanup(void) +{ + do_cleanup(); + pr_info("module unloaded\n"); +} + +/* ========================================================================== */ + +module_init(qc71_laptop_module_init); +module_exit(qc71_laptop_module_cleanup); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Barnabás Pőcze "); +MODULE_DESCRIPTION("QC71 laptop platform driver"); +MODULE_ALIAS("wmi:" QC71_WMI_WMBC_GUID); diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/Makefile slimbook-qc71-0.2/qc71_laptop-0.1/Makefile --- slimbook-qc71-0.1/qc71_laptop-0.1/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/Makefile 2022-08-17 10:06:50.000000000 +0000 @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0 +MODNAME = qc71_laptop +MODVER = 0.0 + +obj-m += $(MODNAME).o + +# alphabetically sorted +$(MODNAME)-y += ec.o \ + features.o \ + main.o \ + misc.o \ + pdev.o \ + events.o \ + +$(MODNAME)-$(CONFIG_DEBUG_FS) += debugfs.o +$(MODNAME)-$(CONFIG_ACPI_BATTERY) += battery.o +$(MODNAME)-$(CONFIG_LEDS_CLASS) += led_lightbar.o +$(MODNAME)-$(CONFIG_HWMON) += hwmon.o hwmon_fan.o hwmon_pwm.o fan.o + +KVER = $(shell uname -r) +KDIR = /lib/modules/$(KVER)/build +MDIR = /usr/src/$(MODNAME)-$(MODVER) + +all: + make -C $(KDIR) M=$(PWD) modules + +clean: + make -C $(KDIR) M=$(PWD) clean + +dkmsinstall: + mkdir -p $(MDIR) + cp Makefile dkms.conf $(wildcard *.c) $(wildcard *.h) $(MDIR)/. + dkms add $(MODNAME)/$(MODVER) + dkms build $(MODNAME)/$(MODVER) + dkms install $(MODNAME)/$(MODVER) + +dkmsuninstall: + -rmmod $(MODNAME) + -dkms uninstall $(MODNAME)/$(MODVER) + -dkms remove $(MODNAME)/$(MODVER) --all + rm -rf $(MDIR) diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/misc.c slimbook-qc71-0.2/qc71_laptop-0.1/misc.c --- slimbook-qc71-0.1/qc71_laptop-0.1/misc.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/misc.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include + +#include "ec.h" +#include "misc.h" +#include "util.h" + +/* ========================================================================== */ + +int qc71_rfkill_get_wifi_state(void) +{ + int err = ec_read_byte(DEVICE_STATUS_ADDR); + + if (err < 0) + return err; + + return !!(err & DEVICE_STATUS_WIFI_ON); +} + +/* ========================================================================== */ + +int qc71_fn_lock_get_state(void) +{ + int status = ec_read_byte(BIOS_CTRL_1_ADDR); + + if (status < 0) + return status; + + return !!(status & BIOS_CTRL_1_FN_LOCK_STATUS); +} + +int qc71_fn_lock_set_state(bool state) +{ + int status = ec_read_byte(BIOS_CTRL_1_ADDR); + + if (status < 0) + return status; + + status = SET_BIT(status, BIOS_CTRL_1_FN_LOCK_STATUS, state); + + return ec_write_byte(BIOS_CTRL_1_ADDR, status); +} diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/misc.h slimbook-qc71-0.2/qc71_laptop-0.1/misc.h --- slimbook-qc71-0.1/qc71_laptop-0.1/misc.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/misc.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_MISC_H +#define QC71_MISC_H + +#include + +/* ========================================================================== */ + +int qc71_rfkill_get_wifi_state(void); + +int qc71_fn_lock_get_state(void); +int qc71_fn_lock_set_state(bool state); + +#endif /* QC71_MISC_H */ diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/pdev.c slimbook-qc71-0.2/qc71_laptop-0.1/pdev.c --- slimbook-qc71-0.1/qc71_laptop-0.1/pdev.c 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/pdev.c 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "pr.h" + +#include +#include +#include +#include +#include + +#include "util.h" +#include "ec.h" +#include "features.h" +#include "misc.h" +#include "pdev.h" + +/* ========================================================================== */ + +struct platform_device *qc71_platform_dev; + +/* ========================================================================== */ + +static ssize_t fan_reduced_duty_cycle_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = ec_read_byte(BIOS_CTRL_3_ADDR); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", !!(status & BIOS_CTRL_3_FAN_REDUCED_DUTY_CYCLE)); +} + +static ssize_t fan_reduced_duty_cycle_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + status = ec_read_byte(BIOS_CTRL_3_ADDR); + if (status < 0) + return status; + + status = SET_BIT(status, BIOS_CTRL_3_FAN_REDUCED_DUTY_CYCLE, value); + + status = ec_write_byte(BIOS_CTRL_3_ADDR, status); + + if (status < 0) + return status; + + return count; +} + +static ssize_t fan_always_on_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = ec_read_byte(BIOS_CTRL_3_ADDR); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", !!(status & BIOS_CTRL_3_FAN_ALWAYS_ON)); +} + +static ssize_t fan_always_on_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + status = ec_read_byte(BIOS_CTRL_3_ADDR); + if (status < 0) + return status; + + status = SET_BIT(status, BIOS_CTRL_3_FAN_ALWAYS_ON, value); + + status = ec_write_byte(BIOS_CTRL_3_ADDR, status); + + if (status < 0) + return status; + + return count; +} + +static ssize_t fn_lock_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = qc71_fn_lock_get_state(); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", status); +} + +static ssize_t fn_lock_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + status = qc71_fn_lock_set_state(value); + if (status < 0) + return status; + + return count; +} + +static ssize_t fn_lock_switch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = ec_read_byte(AP_BIOS_BYTE_ADDR); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", !!(status & AP_BIOS_BYTE_FN_LOCK_SWITCH)); +} + +static ssize_t fn_lock_switch_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + status = ec_read_byte(AP_BIOS_BYTE_ADDR); + if (status < 0) + return status; + + status = SET_BIT(status, AP_BIOS_BYTE_FN_LOCK_SWITCH, value); + + status = ec_write_byte(AP_BIOS_BYTE_ADDR, status); + + if (status < 0) + return status; + + return count; +} + +static ssize_t manual_control_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = ec_read_byte(CTRL_1_ADDR); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", !!(status & CTRL_1_MANUAL_MODE)); +} + +static ssize_t manual_control_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + status = ec_read_byte(CTRL_1_ADDR); + if (status < 0) + return status; + + status = SET_BIT(status, CTRL_1_MANUAL_MODE, value); + + status = ec_write_byte(CTRL_1_ADDR, status); + + if (status < 0) + return status; + + return count; +} + +static ssize_t super_key_lock_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = ec_read_byte(STATUS_1_ADDR); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", !!(status & STATUS_1_SUPER_KEY_LOCK)); +} + +static ssize_t super_key_lock_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + status = ec_read_byte(STATUS_1_ADDR); + if (status < 0) + return status; + + if (value != !!(status & STATUS_1_SUPER_KEY_LOCK)) { + status = ec_write_byte(TRIGGER_1_ADDR, TRIGGER_1_SUPER_KEY_LOCK); + + if (status < 0) + return status; + } + + return count; +} + +static ssize_t fan_boost_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = ec_read_byte(FAN_CTRL_ADDR); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", !!(status & FAN_CTRL_FAN_BOOST)); +} + +static ssize_t fan_boost_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + status = ec_read_byte(FAN_CTRL_ADDR); + if (status < 0) + return status; + + if (value != !!(status & FAN_CTRL_FAN_BOOST)) { + status = ec_write_byte(TRIGGER_1_ADDR, TRIGGER_1_FAN_BOOST); + + if (status < 0) + return status; + } + + return count; +} + +static ssize_t silent_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = ec_read_byte(FAN_CTRL_ADDR); + + if (status < 0) + return status; + + return sprintf(buf, "%d\n", !!(status & FAN_CTRL_SILENT_MODE)); +} + +static ssize_t silent_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + status = ec_read_byte(FAN_CTRL_ADDR); + if (status < 0) + return status; + + status = SET_BIT(status, FAN_CTRL_SILENT_MODE, value); + + status = ec_write_byte(FAN_CTRL_ADDR, status); + + if (status < 0) + return status; + + return count; +} + +/* ========================================================================== */ + +static DEVICE_ATTR_RW(fn_lock); +static DEVICE_ATTR_RW(fn_lock_switch); +static DEVICE_ATTR_RW(fan_always_on); +static DEVICE_ATTR_RW(fan_reduced_duty_cycle); +static DEVICE_ATTR_RW(manual_control); +static DEVICE_ATTR_RW(super_key_lock); +static DEVICE_ATTR_RW(fan_boost); +static DEVICE_ATTR_RW(silent_mode); + +static struct attribute *qc71_laptop_attrs[] = { + &dev_attr_fn_lock.attr, + &dev_attr_fn_lock_switch.attr, + &dev_attr_fan_always_on.attr, + &dev_attr_fan_reduced_duty_cycle.attr, + &dev_attr_manual_control.attr, + &dev_attr_super_key_lock.attr, + &dev_attr_fan_boost.attr, + &dev_attr_silent_mode.attr, + NULL +}; + +/* ========================================================================== */ + +static umode_t qc71_laptop_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + bool ok = false; + + if (attr == &dev_attr_fn_lock.attr || attr == &dev_attr_fn_lock_switch.attr) + ok = qc71_features.fn_lock; + else if (attr == &dev_attr_fan_always_on.attr || attr == &dev_attr_fan_reduced_duty_cycle.attr) + ok = qc71_features.fan_extras; + else if (attr == &dev_attr_manual_control.attr) + ok = true; + else if (attr == &dev_attr_super_key_lock.attr) + ok = qc71_features.super_key_lock; + else if (attr == &dev_attr_fan_boost.attr) + ok = qc71_features.fan_boost; + else if (attr == &dev_attr_silent_mode.attr) + ok = qc71_features.silent_mode; + + return ok ? attr->mode : 0; +} + +/* ========================================================================== */ + +static const struct attribute_group qc71_laptop_group = { + .is_visible = qc71_laptop_attr_is_visible, + .attrs = qc71_laptop_attrs, +}; + +static const struct attribute_group *qc71_laptop_groups[] = { + &qc71_laptop_group, + NULL +}; + +/* ========================================================================== */ + +int __init qc71_pdev_setup(void) +{ + int err; + + qc71_platform_dev = platform_device_alloc(KBUILD_MODNAME, PLATFORM_DEVID_NONE); + if (!qc71_platform_dev) { + err = -ENOMEM; + goto out; + } + + qc71_platform_dev->dev.groups = qc71_laptop_groups; + + err = platform_device_add(qc71_platform_dev); + if (err) { + platform_device_put(qc71_platform_dev); + qc71_platform_dev = NULL; + } + +out: + return err; +} + +void qc71_pdev_cleanup(void) +{ + /* checks for IS_ERR_OR_NULL() */ + platform_device_unregister(qc71_platform_dev); +} diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/pdev.h slimbook-qc71-0.2/qc71_laptop-0.1/pdev.h --- slimbook-qc71-0.1/qc71_laptop-0.1/pdev.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/pdev.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_PDEV_H +#define QC71_PDEV_H + +#include +#include + +/* ========================================================================== */ + +extern struct platform_device *qc71_platform_dev; + +/* ========================================================================== */ + +int __init qc71_pdev_setup(void); +void qc71_pdev_cleanup(void); + +#endif /* QC71_PDEV_H */ diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/pr.h slimbook-qc71-0.2/qc71_laptop-0.1/pr.h --- slimbook-qc71-0.1/qc71_laptop-0.1/pr.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/pr.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_PR_H +#define QC71_PR_H + +#define pr_fmt(fmt) KBUILD_MODNAME "/" KBUILD_BASENAME ": %s: " fmt, __func__ + +#include + +#endif /* QC71_PR_H */ diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/README.md slimbook-qc71-0.2/qc71_laptop-0.1/README.md --- slimbook-qc71-0.1/qc71_laptop-0.1/README.md 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/README.md 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,230 @@ +# What is it? +This a Linux kernel platform driver for Intel Whitebook LAPQC71X systems (XMG Fusion 15, Eluktronics MAG 15, Aftershock Vapor 15, ...). + + +# Disclaimer +**This software is in early stages of developement. Futhermore, to quote GPL: everything is provided as is. There is no warranty for the program, to the extent permitted by applicable law.** + +**This software is licensed under the GNU General Public License v2.0** + + +# Compatibility +It has only been tested on an XMG Fusion 15 device (BIOS 0062 up to 0120) and with the `5.4`, and `5.8`-`5.13` kernel series. +Some functions have been confirmed to work on the Tongfang GK7C chassis (XMG Neo 17, PCS Recoil III, Walmart OP17) (see [#6][issue6]). + + +# Dependencies +### Required +* Your kernel has been compiled with `CONFIG_ACPI` and `CONFIG_DMI` (it probably was) +* Linux headers for you current kernel + +### Optional +* DKMS if you don't want to recompile it manually every time the kernel is upgraded + + +# Features +## Current +* Integrate fan speeds into the Linux hardware monitoring subsystem (so that `lm_sensors` can pick it up) +* Control the lightbar +* Enable/disable always-on mode, reduced fan duty cycle (BIOS 0114 and above) +* Fn lock (BIOS 0114 and above) +* Change battery charge limit (BIOS 0114 and above) + + +# How to install +## Downloading +If you have `git` installed: +``` +git clone https://github.com/pobrn/qc71_laptop +``` + +If you don't, then you can download it [here](https://github.com/pobrn/qc71_laptop/archive/master.zip). + +## Installing +### Linux headers +On Debian and its [many][debian-derivatives] [derivatives][ubuntu-derivatives] (Ubuntu, Pop OS, Linux Mint, ...) , run +``` +sudo apt install linux-headers-$(uname -r) +``` +to install the necessary header files. + +On Arch Linux and its derivatives (Manjaro, ...), run +``` +sudo pacman -Syu linux-headers +``` + +### DKMS (optional) +DKMS should be in your distributions repositories. `sudo apt install dkms`, `sudo pacman -Syu dkms` should work depending on your distribution. + +### The module +#### Manually +Navigate in a terminal into the directory, then execute `make`. This should compile the module. If everything went correctly, a file named `qc71_laptop.ko` should appear in the directory. + +To test the module try `sudo insmod qc71_laptop.ko`. Now you should see the fan speeds appear in the output of `sensors`, and the directory `/sys/devices/platform/qc71_laptop` should now exist. If you are done testing, unload the module using `sudo rmmod qc71_laptop`. + +Now you could create a script that inserts this module at boot from this directory, or you could install it using DKMS. + +#### With DKMS +Run +``` +sudo make dkmsinstall +``` +to install the module with DKMS. Or run +``` +sudo make dkmsuninstall +``` +to uninstall the module. + +The module should automatically load at boot after this. If you want to load it immediately, run `sudo modprobe qc71_laptop`. If it reports an error, and you're convinced your device should be supported, please open an [issue][issues]. + +## Upgrade + +If you installed the module with DKMS, and you wish to upgrade, first open the directory of the old sources, and run +``` +sudo make dkmsuninstall +``` +then update the sources (pull the repository, download the sources again manually, etc.), then run +``` +sudo make dkmsinstall +``` + + +# How to use +## Fan speeds +After loading the module the fan speeds and temperatures should immediately appear in the output of `sensors`, and all your favourite monitoring utilities (e.g. the [Freon][gnome-ext-freon] GNOME shell extension) that use `sensors`. + +## Controlling the lightbar +The lightbar is integrated into the LED subsystem of the linux kernel. When the module is loaded, `/sys/class/leds/qc71_laptop::lightbar` directory should exist with the following important files: +``` +/sys/class/leds/qc71_laptop::lightbar/brightness +``` + +It contains `1` if the lightbar is turned on in S0 sleep state (aka. when the device is powered on), `0` otherwise. You can turn on/off the lightbar by writing an appropriate number into this file: +``` +# echo 1 > /sys/class/leds/qc71_laptop::lightbar/brightness +``` +will turn the lightbar on. (Writing `0` will turn it off.) +To check the current state: +``` +$ cat /sys/class/leds/qc71_laptop::lightbar/brightness +``` + +___ +``` +/sys/class/leds/qc71_laptop::lightbar/brightness_s3 +``` +It contains `1` if the lightbar is turned on in S3 sleep state (aka. when the device is sleeping). If it is `1`, the lightbar will "breathe" when the device is sleeping. You can control it the same way as `brightness`. + +___ +``` +/sys/class/leds/qc71_laptop::lightbar/rainbow_mode +``` +It contains `1` if the "rainbow mode" is enabled (aka. the color will continuously cycle). Controlling works the same way as before. + +*Note:* Enabling/disabling the rainbow mode will not turn the lightbar on/off. +*Note:* The rainbow mode takes precedence over the color. + +___ +``` +/sys/class/leds/qc71_laptop::lightbar/color +``` +This file controls the color of the lightbar. It is a three digit number (possibly with padding zeroes). The first digit is the red component, the second one is the green componenet, the third one is the blue component. +``` +$ cat /sys/class/leds/qc71_laptop::lightbar/color +``` +tells you the current color, while +``` +# echo 591 > /sys/class/leds/qc71_laptop::lightbar/color +``` +will change the current color. + +*Note:* Chaning the color will not turn the lightbar on. + + +## Controlling the fans +These can be controlled directly from the BIOS as well. + +### Passive cooling +I call this feature "always on" because that's less confusing than "passive cooling". +``` +# echo 1 > /sys/devices/platform/qc71_laptop/fan_always_on +``` +will cause the fans to run continuously. Writing `0` will turn it off. + +### Reduced duty cycle +``` +# echo 1 > /sys/devices/platform/qc71_laptop/fan_reduced_duty_cycle +``` +will cause the fans to run at 25% of their capacity (about 2300 RPM) at idle (instead of 30% - about 2700 RPM). Writing `0` will restore the 30% idle duty cycle. + +## Fn lock +``` +# echo 1 > /sys/devices/platform/qc71_laptop/fn_lock_switch +``` +will enable changing the Fn lock state by pressing Fn+ESC. If this file contains `1`, then pressing Fn+ESC will toggle the Fn lock; if this file contains `0`, then pressing Fn+ESC will have no effect on the Fn lock. + +``` +# echo 1 > /sys/devices/platform/qc71_laptop/fn_lock +``` +will directly enable the Fn lock. If this file contains `1`, then pressing the functions keys will trigger their secondary functions (mute, brightness up, etc.); if this file contains `0`, then pressing the functions keys will trigger their primary functions (F1, F2, ...). + +## Battery charge limit +The file `/sys/class/power_supply/BAT0/charge_control_end_threshold` contains the current charge threshold. Writing a number between 1 and 100 will cause the battery charging limit to be set to that percentage. (I did not test extremely low values, so I cannot say if they work). For example: +``` +# echo 60 > /sys/class/power_supply/BAT0/charge_control_end_threshold +``` +will cause charging to stop when the battery reaches 60% of its capacity. + +## Super key (windows key) lock +It is possible to disable the super (windows) key by pressing Fn+F2 (or just F2 if the Fn lock is enabled). This can be also achieved by changing the writing the appropriate value into `/sys/devices/platform/qc71_laptop/super_key_lock`. +``` +# echo 1 > /sys/devices/platform/qc71_laptop/super_key_lock +``` +disables the super key, while +``` +# echo 0 > /sys/devices/platform/qc71_laptop/super_key_lock +``` +enables it. Reading the file will provide information about the current state of the super key. `0` means enabled, `1` means disabled. + +## Example use + +The XMG Control Center can change the color if the device is on battery or plugged in. Fortunately you can easily achieve the same using [acpid](https://wiki.archlinux.org/index.php/Acpid). Modifying the appropriate part of `/etc/acpi/handler.sh` like this: +``` + ac_adapter) + case "$2" in + AC|ACAD|ADP0|ACPI0003:00) # the "ACPI0003:00" part was not there by default + case "$4" in + 00000000) + logger 'AC unplugged' + + # change color to red + echo 900 > /sys/class/leds/qc71_laptop::lightbar/color + ;; + 00000001) + logger 'AC plugged' + + # change color to blue + echo 009 > /sys/class/leds/qc71_laptop::lightbar/color + ;; + esac + ;; + *) + logger "ACPI action undefined: $2" + ;; + esac + ;; +``` +You can use `acpi_listen` to see what events are generated when you plug the machine in or disconnect the charger. You might need to modify the third line (in this snippet). + + +# Troubleshooting + +* The [TUXEDO Control Center][tcc-github] may interfere with the operation of this kernel module. I do not recommend using both at the same time. + + +[issue6]: https://github.com/pobrn/qc71_laptop/issues/6 +[debian-derivatives]: https://www.debian.org/derivatives/ +[ubuntu-derivatives]: https://wiki.ubuntu.com/DerivativeTeam/Derivatives +[issues]: https://github.com/pobrn/qc71_laptop/issues +[gnome-ext-freon]: https://extensions.gnome.org/extension/841/freon/ +[tcc-github]: https://github.com/tuxedocomputers/tuxedo-control-center diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/util.h slimbook-qc71-0.2/qc71_laptop-0.1/util.h --- slimbook-qc71-0.1/qc71_laptop-0.1/util.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/util.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_UTIL_H +#define QC71_UTIL_H + +#define SET_BIT(value, bit, on) ((on) ? ((value) | (bit)) : ((value) & ~(bit))) + +#endif /* QC71_UTIL_H */ diff -Nru slimbook-qc71-0.1/qc71_laptop-0.1/wmi.h slimbook-qc71-0.2/qc71_laptop-0.1/wmi.h --- slimbook-qc71-0.1/qc71_laptop-0.1/wmi.h 1970-01-01 00:00:00.000000000 +0000 +++ slimbook-qc71-0.2/qc71_laptop-0.1/wmi.h 2022-08-17 10:06:06.000000000 +0000 @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef QC71_LAPTOP_WMI_H +#define QC71_LAPTOP_WMI_H + +/* ========================================================================== */ +/* WMI methods */ + +/* AcpiTest_MULong */ +#define QC71_WMI_WMBC_GUID "ABBC0F6F-8EA1-11D1-00A0-C90629100000" +#define QC71_WMBC_GETSETULONG_ID 4 + +/* ========================================================================== */ +/* WMI events */ + +/* AcpiTest_EventULong */ +#define QC71_WMI_EVENT0_GUID "ABBC0F72-8EA1-11D1-00A0-C90629100000" + +/* AcpiTest_EventString */ +#define QC71_WMI_EVENT1_GUID "ABBC0F71-8EA1-11D1-00A0-C90629100000" + +/* AcpiTest_EventPackage */ +#define QC71_WMI_EVENT2_GUID "ABBC0F70-8EA1-11D1-00A0-C90629100000" + +#endif /* QC71_LAPTOP_WMI_H */ diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/battery.c slimbook-qc71-0.2/slimbook-qc71-0.0/battery.c --- slimbook-qc71-0.1/slimbook-qc71-0.0/battery.c 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/battery.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "pr.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "ec.h" -#include "features.h" - -/* ========================================================================== */ - -#if IS_ENABLED(CONFIG_ACPI_BATTERY) - -static bool battery_hook_registered; - -static bool nobattery; -module_param(nobattery, bool, 0444); -MODULE_PARM_DESC(nobattery, "do not expose battery related controls (default=false)"); - -/* ========================================================================== */ - -static ssize_t charge_control_end_threshold_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int status = ec_read_byte(BATT_CHARGE_CTRL_ADDR); - - if (status < 0) - return status; - - status &= BATT_CHARGE_CTRL_VALUE_MASK; - - if (status == 0) - status = 100; - - return sprintf(buf, "%d\n", status); -} - -static ssize_t charge_control_end_threshold_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - int status, value; - - if (kstrtoint(buf, 10, &value) || !(1 <= value && value <= 100)) - return -EINVAL; - - status = ec_read_byte(BATT_CHARGE_CTRL_ADDR); - if (status < 0) - return status; - - if (value == 100) - value = 0; - - status = (status & ~BATT_CHARGE_CTRL_VALUE_MASK) | value; - - status = ec_write_byte(BATT_CHARGE_CTRL_ADDR, status); - - if (status < 0) - return status; - - return count; -} - -static DEVICE_ATTR_RW(charge_control_end_threshold); -static struct attribute *qc71_laptop_batt_attrs[] = { - &dev_attr_charge_control_end_threshold.attr, - NULL -}; -ATTRIBUTE_GROUPS(qc71_laptop_batt); - -static int qc71_laptop_batt_add(struct power_supply *battery) -{ - if (strcmp(battery->desc->name, "BAT0") != 0) - return 0; - - return device_add_groups(&battery->dev, qc71_laptop_batt_groups); -} - -static int qc71_laptop_batt_remove(struct power_supply *battery) -{ - if (strcmp(battery->desc->name, "BAT0") != 0) - return 0; - - device_remove_groups(&battery->dev, qc71_laptop_batt_groups); - return 0; -} - -static struct acpi_battery_hook qc71_laptop_batt_hook = { - .add_battery = qc71_laptop_batt_add, - .remove_battery = qc71_laptop_batt_remove, - .name = "QC71 laptop battery extension", -}; - -int __init qc71_battery_setup(void) -{ - if (nobattery || !qc71_features.batt_charge_limit) - return -ENODEV; - - battery_hook_register(&qc71_laptop_batt_hook); - battery_hook_registered = true; - - return 0; -} - -void qc71_battery_cleanup(void) -{ - if (battery_hook_registered) - battery_hook_unregister(&qc71_laptop_batt_hook); -} - -#endif diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/battery.h slimbook-qc71-0.2/slimbook-qc71-0.0/battery.h --- slimbook-qc71-0.1/slimbook-qc71-0.0/battery.h 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/battery.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef QC71_BATTERY_H -#define QC71_BATTERY_H - -#if IS_ENABLED(CONFIG_ACPI_BATTERY) - -#include - -int __init qc71_battery_setup(void); -void qc71_battery_cleanup(void); - -#else - -static inline int qc71_battery_setup(void) -{ - return 0; -} - -static inline void qc71_battery_cleanup(void) -{ - -} - -#endif - -#endif /* QC71_BATTERY_H */ diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/debugfs.c slimbook-qc71-0.2/slimbook-qc71-0.0/debugfs.c --- slimbook-qc71-0.1/slimbook-qc71-0.0/debugfs.c 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/debugfs.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,260 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "pr.h" - -#include -#include -#include -#include -#include -#include - -#include "debugfs.h" -#include "ec.h" - -#if IS_ENABLED(CONFIG_DEBUG_FS) - -#define DEBUGFS_DIR_NAME KBUILD_MODNAME - -static const struct qc71_debugfs_attr { - const char *name; - uint16_t addr; -} qc71_debugfs_attrs[] = { - {"1108", 1108}, - - {"ap_bios_byte", AP_BIOS_BYTE_ADDR}, - - {"batt_alert", BATT_ALERT_ADDR}, - {"batt_charge_ctrl", BATT_CHARGE_CTRL_ADDR}, - {"batt_status", BATT_STATUS_ADDR}, - {"battt_temp", BATT_TEMP_ADDR}, - {"bios_ctrl_1", BIOS_CTRL_1_ADDR}, - {"bios_ctrl_2", BIOS_CTRL_2_ADDR}, - {"bios_ctrl_3", BIOS_CTRL_3_ADDR}, - {"bios_info_1", BIOS_INFO_1_ADDR}, - {"bios_info_5", BIOS_INFO_5_ADDR}, - - {"ctrl_1", CTRL_1_ADDR}, - {"ctrl_2", CTRL_2_ADDR}, - {"ctrl_3", CTRL_3_ADDR}, - {"ctrl_4", CTRL_4_ADDR}, - {"ctrl_5", CTRL_5_ADDR}, - {"ctrl_6", CTRL_6_ADDR}, - - {"device_status", DEVICE_STATUS_ADDR}, - - {"fan_ctrl", FAN_CTRL_ADDR}, - {"fan_mode_index", FAN_MODE_INDEX_ADDR}, - {"fan_temp_1", FAN_TEMP_1_ADDR}, - {"fan_temp_2", FAN_TEMP_2_ADDR}, - {"fan_pwm_1", FAN_PWM_1_ADDR}, - {"fan_pwm_2", FAN_PWM_2_ADDR}, - - /* setting these don't seem to work */ - {"fan_l1_pwm", ADDR(0x07, 0x43)}, - {"fan_l2_pwm", ADDR(0x07, 0x44)}, - {"fan_l3_pwm", ADDR(0x07, 0x45)}, - /* seemingly there is another level here, fan_ctrl=0x84, pwm=0x5a */ - {"fan_l4_pwm", ADDR(0x07, 0x46)}, - {"fan_l5_pwm", ADDR(0x07, 0x47)}, /* this is seemingly ignored, fan_ctrl=0x86, pwm=0xb4 */ - - {"fan_l1_pwm_default", ADDR(0x07, 0x86)}, - {"fan_l2_pwm_default", ADDR(0x07, 0x87)}, - {"fan_l3_pwm_default", ADDR(0x07, 0x88)}, - {"fan_l4_pwm_default", ADDR(0x07, 0x89)}, - {"fan_l5_pwm_default", ADDR(0x07, 0x8A)}, - - /* these don't seem to work */ - {"fan_min_speed", 1950}, - {"fan_min_temp", 1951}, - {"fan_extra_speed", 1952}, - - {"lightbar_ctrl", LIGHTBAR_CTRL_ADDR}, - {"lightbar_red", LIGHTBAR_RED_ADDR}, - {"lightbar_green", LIGHTBAR_GREEN_ADDR}, - {"lightbar_blue", LIGHTBAR_BLUE_ADDR}, - - {"keyboard_type", KEYBOARD_TYPE_ADDR}, - - {"support_1", SUPPORT_1_ADDR}, - {"support_2", SUPPORT_2_ADDR}, - {"support_5", SUPPORT_5_ADDR}, - {"status_1", STATUS_1_ADDR}, - - {"platform_id", PLATFORM_ID_ADDR}, - {"power_source", POWER_SOURCE_ADDR}, - {"project_id", PROJ_ID_ADDR}, - {"power_status", POWER_STATUS_ADDR}, - {"pl_1", PL1_ADDR}, - {"pl_2", PL2_ADDR}, - {"pl_4", PL4_ADDR}, - - {"trigger_1", TRIGGER_1_ADDR}, - {"trigger_2", TRIGGER_2_ADDR}, -}; - -/* ========================================================================== */ - -static bool debugregs; -module_param(debugregs, bool, 0444); -MODULE_PARM_DESC(debugregs, "expose various EC registers in debugfs (default=false)"); - -static struct dentry *qc71_debugfs_dir, - *qc71_debugfs_regs_dir; - -/* ========================================================================== */ - -static int get_debugfs_byte(void *data, u64 *value) -{ - const struct qc71_debugfs_attr *attr = data; - int status = ec_read_byte(attr->addr); - - if (status < 0) - return status; - - *value = status; - - return 0; -} - -static int set_debugfs_byte(void *data, u64 value) -{ - const struct qc71_debugfs_attr *attr = data; - int status; - - if (value > U8_MAX) - return -EINVAL; - - status = ec_write_byte(attr->addr, (uint8_t) value); - - if (status < 0) - return status; - - return status; -} - -DEFINE_DEBUGFS_ATTRIBUTE(qc71_debugfs_fops, get_debugfs_byte, set_debugfs_byte, "0x%02llx\n"); - -/* ========================================================================== */ - -static ssize_t qc71_debugfs_ec_read(struct file *f, char __user *buf, size_t count, loff_t *offset) -{ - size_t i; - - for (i = 0; *offset + i < U16_MAX && i < count; i++) { - int err = ec_read_byte(*offset + i); - u8 byte; - - if (signal_pending(current)) - return -EINTR; - - if (err < 0) { - if (i) - break; - - return err; - } - - byte = err; - - if (put_user(byte, buf + i)) - return -EFAULT; - } - - *offset += i; - - return i; -} - -static ssize_t qc71_debugfs_ec_write(struct file *f, const char __user *buf, size_t count, loff_t *offset) -{ - size_t i; - - for (i = 0; *offset + i < U16_MAX && i < count; i++) { - int err; - u8 byte; - - if (get_user(byte, buf + i)) - return -EFAULT; - - err = ec_write_byte(*offset + i, byte); - if (err) { - if (i) - break; - - return err; - } - - if (signal_pending(current)) - return -EINTR; - } - - *offset += i; - - return i; -} - -static const struct file_operations qc71_debugfs_ec_fops = { - .owner = THIS_MODULE, - .open = simple_open, - .read = qc71_debugfs_ec_read, - .write = qc71_debugfs_ec_write, - .llseek = default_llseek, -}; - -/* ========================================================================== */ - -int __init qc71_debugfs_setup(void) -{ - struct dentry *d; - int err = 0; - size_t i; - - if (!debugregs) - return -ENODEV; - - qc71_debugfs_dir = debugfs_create_dir(DEBUGFS_DIR_NAME, NULL); - - if (IS_ERR(qc71_debugfs_dir)) { - err = PTR_ERR(qc71_debugfs_dir); - goto out; - } - - qc71_debugfs_regs_dir = debugfs_create_dir("regs", qc71_debugfs_dir); - - if (IS_ERR(qc71_debugfs_regs_dir)) { - err = PTR_ERR(qc71_debugfs_regs_dir); - debugfs_remove_recursive(qc71_debugfs_dir); - goto out; - } - - for (i = 0; i < ARRAY_SIZE(qc71_debugfs_attrs); i++) { - const struct qc71_debugfs_attr *attr = &qc71_debugfs_attrs[i]; - - d = debugfs_create_file(attr->name, 0600, qc71_debugfs_regs_dir, - (void *) attr, &qc71_debugfs_fops); - - if (IS_ERR(d)) { - err = PTR_ERR(d); - debugfs_remove_recursive(qc71_debugfs_dir); - goto out; - } - } - - d = debugfs_create_file("ec", 0600, qc71_debugfs_dir, NULL, &qc71_debugfs_ec_fops); - if (IS_ERR(d)) { - err = PTR_ERR(d); - debugfs_remove_recursive(qc71_debugfs_dir); - goto out; - } - -out: - return err; -} - -void qc71_debugfs_cleanup(void) -{ - /* checks if IS_ERR_OR_NULL() */ - debugfs_remove_recursive(qc71_debugfs_dir); -} - -#endif diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/debugfs.h slimbook-qc71-0.2/slimbook-qc71-0.0/debugfs.h --- slimbook-qc71-0.1/slimbook-qc71-0.0/debugfs.h 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/debugfs.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef QC71_DEBUGFS_H -#define QC71_DEBUGFS_H - -#if IS_ENABLED(CONFIG_DEBUG_FS) - -#include - -int __init qc71_debugfs_setup(void); -void qc71_debugfs_cleanup(void); - -#else - -static inline int qc71_debugfs_setup(void) -{ - return 0; -} - -static inline void qc71_debugfs_cleanup(void) -{ - -} - -#endif - -#endif /* QC71_DEBUGFS_H */ diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/dkms.conf slimbook-qc71-0.2/slimbook-qc71-0.0/dkms.conf --- slimbook-qc71-0.1/slimbook-qc71-0.0/dkms.conf 2022-08-17 13:01:25.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/dkms.conf 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -MAKE="make KDIR=${kernel_source_dir}" -CLEAN="make KDIR=${kernel_source_dir} clean" -BUILT_MODULE_NAME=qc71_laptop -PACKAGE_NAME=slimbook-qc71 -PACKAGE_VERSION=0.0 -DEST_MODULE_LOCATION=/kernel/drivers/platform/x86 -AUTOINSTALL=yes diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/ec.c slimbook-qc71-0.2/slimbook-qc71-0.0/ec.c --- slimbook-qc71-0.1/slimbook-qc71-0.0/ec.c 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/ec.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "pr.h" - -#include -#include -#include -#include -#include -#include - -#include "ec.h" -#include "wmi.h" - -/* ========================================================================== */ - -static DECLARE_RWSEM(ec_lock); - -/* ========================================================================== */ - -int __must_check qc71_ec_lock(void) -{ - return down_write_killable(&ec_lock); -} - -void qc71_ec_unlock(void) -{ - up_write(&ec_lock); -} - -int __must_check qc71_ec_transaction(uint16_t addr, uint16_t data, - union qc71_ec_result *result, bool read) -{ - uint8_t buf[] = { - addr & 0xFF, - addr >> 8, - data & 0xFF, - data >> 8, - 0, - read ? 1 : 0, - 0, - 0, - }; - static_assert(ARRAY_SIZE(buf) == 8); - - /* the returned ACPI_TYPE_BUFFER is 40 bytes long for some reason ... */ - uint8_t output_buf[sizeof(union acpi_object) + 40]; - - struct acpi_buffer input = { sizeof(buf), buf }, - output = { sizeof(output_buf), output_buf }; - union acpi_object *obj; - acpi_status status = AE_OK; - int err; - - if (read) err = down_read_killable(&ec_lock); - else err = down_write_killable(&ec_lock); - - if (err) - goto out; - - memset(output_buf, 0, sizeof(output_buf)); - - status = wmi_evaluate_method(QC71_WMI_WMBC_GUID, 0, - QC71_WMBC_GETSETULONG_ID, &input, &output); - - if (read) up_read(&ec_lock); - else up_write(&ec_lock); - - if (ACPI_FAILURE(status)) { - err = -EIO; - goto out; - } - - obj = output.pointer; - - if (result) { - if (obj && obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= sizeof(*result)) { - memcpy(result, obj->buffer.pointer, sizeof(*result)); - } else { - err = -ENODATA; - goto out; - } - } - -out: - pr_debug( - "%s(addr=%#06x, data=%#06x, result=%c, read=%c)" - ": (%d) [%#010lx] %s" - ": [%*ph]\n", - - __func__, (unsigned int) addr, (unsigned int) data, - result ? 'y' : 'n', read ? 'y' : 'n', - err, (unsigned long) status, acpi_format_exception(status), - (obj && obj->type == ACPI_TYPE_BUFFER) ? - (int) min(sizeof(*result), (size_t) obj->buffer.length) : 0, - (obj && obj->type == ACPI_TYPE_BUFFER) ? - obj->buffer.pointer : NULL - ); - - return err; -} -ALLOW_ERROR_INJECTION(qc71_ec_transaction, ERRNO); diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/ec.h slimbook-qc71-0.2/slimbook-qc71-0.0/ec.h --- slimbook-qc71-0.1/slimbook-qc71-0.0/ec.h 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/ec.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,272 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef QC71_LAPTOP_EC_H -#define QC71_LAPTOP_EC_H - -#include -#include - -/* ========================================================================== */ -/* - * EC register addresses and bitmasks, - * some of them are not used, - * only for documentation - */ - -#define ADDR(page, offset) (((uint16_t)(page) << 8) | ((uint16_t)(offset))) - -/* ========================================================================== */ - -#define AP_BIOS_BYTE_ADDR ADDR(0x07, 0xA4) -#define AP_BIOS_BYTE_FN_LOCK_SWITCH BIT(3) - -/* ========================================================================== */ - -/* battery charger control register */ -#define BATT_CHARGE_CTRL_ADDR ADDR(0x07, 0xB9) -#define BATT_CHARGE_CTRL_VALUE_MASK GENMASK(6, 0) -#define BATT_CHARGE_CTRL_REACHED BIT(7) - -#define BATT_STATUS_ADDR ADDR(0x04, 0x32) -#define BATT_STATUS_DISCHARGING BIT(0) - -/* possibly temp (in C) = value / 10 + X */ -#define BATT_TEMP_ADDR ADDR(0x04, 0xA2) - -#define BATT_ALERT_ADDR ADDR(0x04, 0x94) - -#define BIOS_CTRL_1_ADDR ADDR(0x07, 0x4E) -#define BIOS_CTRL_1_FN_LOCK_STATUS BIT(4) - -#define BIOS_CTRL_2_ADDR ADDR(0x07, 0x82) -#define BIOS_CTRL_2_FAN_V2_NEW BIT(0) -#define BIOS_CTRL_2_FAN_QKEY BIT(1) -#define BIOS_CTRL_2_OFFICE_MODE_FAN_TABLE_TYPE BIT(2) -#define BIOS_CTRL_2_FAN_V3 BIT(3) -#define BIOS_CTRL_2_DEFAULT_MODE BIT(4) - -/* 3rd control register of a different kind */ -#define BIOS_CTRL_3_ADDR ADDR(0x7, 0xA3) -#define BIOS_CTRL_3_FAN_REDUCED_DUTY_CYCLE BIT(5) -#define BIOS_CTRL_3_FAN_ALWAYS_ON BIT(6) - -#define BIOS_INFO_1_ADDR ADDR(0x04, 0x9F) -#define BIOS_INFO_5_ADDR ADDR(0x04, 0x66) - -/* ========================================================================== */ - -#define CTRL_1_ADDR ADDR(0x07, 0x41) -#define CTRL_1_MANUAL_MODE BIT(0) -#define CTRL_1_ITE_KBD_EFFECT_REACTIVE BIT(3) -#define CTRL_1_FAN_ABNORMAL BIT(5) - -#define CTRL_2_ADDR ADDR(0x07, 0x8C) -#define CTRL_2_SINGLE_COLOR_KEYBOARD BIT(0) -#define CTRL_2_SINGLE_COLOR_KBD_BL_OFF BIT(1) -#define CTRL_2_TURBO_LEVEL_MASK GENMASK(3, 2) -#define CTRL_2_TURBO_LEVEL_0 0x00 -#define CTRL_2_TURBO_LEVEL_1 BIT(2) -#define CTRL_2_TURBO_LEVEL_2 BIT(3) -#define CTRL_2_TURBO_LEVEL_3 (BIT(2) | BIT(3)) -// #define CTRL_2_SINGLE_COLOR_KBD_? BIT(4) -#define CTRL_2_SINGLE_COLOR_KBD_BRIGHTNESS GENMASK(7, 5) - -#define CTRL_3_ADDR ADDR(0x07, 0xA5) -#define CTRL_3_PWR_LED_MASK GENMASK(1, 0) -#define CTRL_3_PWR_LED_NONE BIT(1) -#define CTRL_3_PWR_LED_BOTH BIT(0) -#define CTRL_3_PWR_LED_LEFT 0x00 -#define CTRL_3_FAN_QUIET BIT(2) -#define CTRL_3_OVERBOOST BIT(4) -#define CTRL_3_HIGH_PWR BIT(7) - -#define CTRL_4_ADDR ADDR(0x07, 0xA6) -#define CTRL_4_OVERBOOST_DYN_TEMP_OFF BIT(1) -#define CTRL_4_TOUCHPAD_TOGGLE_OFF BIT(6) - -#define CTRL_5_ADDR ADDR(0x07, 0xC5) - -#define CTRL_6_ADDR ADDR(0x07, 0x8E) - -/* ========================================================================== */ - -#define DEVICE_STATUS_ADDR ADDR(0x04, 0x7B) -#define DEVICE_STATUS_WIFI_ON BIT(7) -/* BIT(5) is seemingly also (un)set depending on the rfkill state (bluetooth?) */ - -/* ========================================================================== */ - -/* fan control register */ -#define FAN_CTRL_ADDR ADDR(0x07, 0x51) -#define FAN_CTRL_LEVEL_MASK GENMASK(2, 0) -#define FAN_CTRL_TURBO BIT(4) -#define FAN_CTRL_AUTO BIT(5) -#define FAN_CTRL_FAN_BOOST BIT(6) -#define FAN_CTRL_SILENT_MODE BIT(7) - -#define FAN_RPM_1_ADDR ADDR(0x04, 0x64) -#define FAN_RPM_2_ADDR ADDR(0x04, 0x6C) - -#define FAN_PWM_1_ADDR ADDR(0x18, 0x04) -#define FAN_PWM_2_ADDR ADDR(0x18, 0x09) - -#define FAN_TEMP_1_ADDR ADDR(0x04, 0x3e) -#define FAN_TEMP_2_ADDR ADDR(0x04, 0x4f) - -#define FAN_MODE_INDEX_ADDR ADDR(0x07, 0xAB) -#define FAN_MODE_INDEX_LOW_MASK GENMASK(3, 0) -#define FAN_MODE_INDEX_HIGH_MASK GENMASK(7, 4) - -/* ========================================================================== */ - -/* - * the actual keyboard type is seemingly determined from this number, - * the project id, the controller firmware version, - * and the HID usage page of the descriptor of the controller - */ -#define KEYBOARD_TYPE_ADDR ADDR(0x07, 0x3C) -#define KEYBOARD_TYPE_101 25 -#define KEYBOARD_TYPE_101M 41 -#define KEYBOARD_TYPE_102 17 -#define KEYBOARD_TYPE_102M 33 -#define KEYBOARD_TYPE_85 25 -#define KEYBOARD_TYPE_86 17 -#define KEYBOARD_TYPE_87 73 -#define KEYBOARD_TYPE_88 65 -#define KEYBOARD_TYPE_97 57 -#define KEYBOARD_TYPE_98 49 -#define KEYBOARD_TYPE_99 121 -#define KEYBOARD_TYPE_100 113 - -/* ========================================================================== */ - -/* lightbar control register */ -#define LIGHTBAR_CTRL_ADDR ADDR(0x07, 0x48) -#define LIGHTBAR_CTRL_POWER_SAVE BIT(1) -#define LIGHTBAR_CTRL_S0_OFF BIT(2) -#define LIGHTBAR_CTRL_S3_OFF BIT(3) -#define LIGHTBAR_CTRL_RAINBOW BIT(7) - -#define LIGHTBAR_RED_ADDR ADDR(0x07, 0x49) -#define LIGHTBAR_GREEN_ADDR ADDR(0x07, 0x4A) -#define LIGHTBAR_BLUE_ADDR ADDR(0x07, 0x4B) - -/* ========================================================================== */ - -#define PROJ_ID_ADDR ADDR(0x07, 0x40) -#define PROJ_ID_GIxKN 1 -#define PROJ_ID_GJxKN 2 -#define PROJ_ID_GKxCN 3 -#define PROJ_ID_GIxCN 4 -#define PROJ_ID_GJxCN 5 -#define PROJ_ID_GK5CN_X 6 -#define PROJ_ID_GK7CN_S 7 -#define PROJ_ID_GK7CPCS_GK5CQ7Z 8 -#define PROJ_ID_PF5NU1G_PF4LUXF 9 -#define PROJ_ID_IDP 11 -#define PROJ_ID_ID6Y 12 -#define PROJ_ID_ID7Y 13 -#define PROJ_ID_PF4MU_PF4MN_PF5MU 14 -#define PROJ_ID_CML_GAMING 15 -#define PROJ_ID_GK7NXXR 16 -#define PROJ_ID_GM5MU1Y 17 - -/* ========================================================================== */ - -#define STATUS_1_ADDR ADDR(0x07, 0x68) -#define STATUS_1_SUPER_KEY_LOCK BIT(0) -#define STATUS_1_LIGHTBAR BIT(1) -#define STATUS_1_FAN_BOOST BIT(2) - -#define SUPPORT_1_ADDR ADDR(0x07, 0x65) -#define SUPPORT_1_AIRPLANE_MODE BIT(0) -#define SUPPORT_1_GPS_SWITCH BIT(1) -#define SUPPORT_1_OVERCLOCK BIT(2) -#define SUPPORT_1_MACRO_KEY BIT(3) -#define SUPPORT_1_SHORTCUT_KEY BIT(4) -#define SUPPORT_1_SUPER_KEY_LOCK BIT(5) -#define SUPPORT_1_LIGHTBAR BIT(6) -#define SUPPORT_1_FAN_BOOST BIT(7) - -#define SUPPORT_2_ADDR ADDR(0x07, 0x66) -#define SUPPORT_2_SILENT_MODE BIT(0) -#define SUPPORT_2_USB_CHARGING BIT(1) -#define SUPPORT_2_SINGLE_ZONE_KBD BIT(2) -#define SUPPORT_2_CHINA_MODE BIT(5) -#define SUPPORT_2_MY_BATTERY BIT(6) - -#define SUPPORT_5_ADDR ADDR(0x07, 0x42) -#define SUPPORT_5_FAN_TURBO BIT(4) -#define SUPPORT_5_FAN BIT(5) - -#define SUPPORT_6 ADDR(0x07, 0x8E) -#define SUPPORT_6_FAN3P5 BIT(1) - -/* ========================================================================== */ - -#define TRIGGER_1_ADDR ADDR(0x07, 0x67) -#define TRIGGER_1_SUPER_KEY_LOCK BIT(0) -#define TRIGGER_1_LIGHTBAR BIT(1) -#define TRIGGER_1_FAN_BOOST BIT(2) -#define TRIGGER_1_SILENT_MODE BIT(3) -#define TRIGGER_1_USB_CHARGING BIT(4) - -#define TRIGGER_2_ADDR ADDR(0x07, 0x5D) - -/* ========================================================================== */ - -#define PLATFORM_ID_ADDR ADDR(0x04, 0x56) -#define POWER_STATUS_ADDR ADDR(0x04, 0x5E) -#define POWER_SOURCE_ADDR ADDR(0x04, 0x90) - -#define PL1_ADDR ADDR(0x07, 0x83) -#define PL2_ADDR ADDR(0x07, 0x84) -#define PL4_ADDR ADDR(0x07, 0x85) - -/* ========================================================================== */ - -union qc71_ec_result { - uint32_t dword; - struct { - uint16_t w1; - uint16_t w2; - } words; - struct { - uint8_t b1; - uint8_t b2; - uint8_t b3; - uint8_t b4; - } bytes; -}; - -int __must_check qc71_ec_transaction(uint16_t addr, uint16_t data, - union qc71_ec_result *result, bool read); - -static inline __must_check int qc71_ec_read(uint16_t addr, union qc71_ec_result *result) -{ - return qc71_ec_transaction(addr, 0, result, true); -} - -static inline __must_check int qc71_ec_write(uint16_t addr, uint16_t data) -{ - return qc71_ec_transaction(addr, data, NULL, false); -} - -static inline __must_check int ec_write_byte(uint16_t addr, uint8_t data) -{ - return qc71_ec_write(addr, data); -} - -static inline __must_check int ec_read_byte(uint16_t addr) -{ - union qc71_ec_result result; - int err; - - err = qc71_ec_read(addr, &result); - - if (err) - return err; - - return result.bytes.b1; -} - -#endif /* QC71_LAPTOP_EC_H */ diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/events.c slimbook-qc71-0.2/slimbook-qc71-0.0/events.c --- slimbook-qc71-0.1/slimbook-qc71-0.0/events.c 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/events.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,400 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "pr.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "misc.h" -#include "pdev.h" -#include "wmi.h" - -/* ========================================================================== */ - -#define KBD_BL_LED_SUFFIX ":" LED_FUNCTION_KBD_BACKLIGHT - -/* ========================================================================== */ - -static struct { - const char *guid; - bool handler_installed; -} qc71_wmi_event_guids[] = { - { .guid = QC71_WMI_EVENT0_GUID }, - { .guid = QC71_WMI_EVENT1_GUID }, - { .guid = QC71_WMI_EVENT2_GUID }, -}; - -static const struct key_entry qc71_wmi_hotkeys[] = { - - /* reported via keyboard controller */ - { KE_IGNORE, 0x01, { KEY_CAPSLOCK }}, - { KE_IGNORE, 0x02, { KEY_NUMLOCK }}, - { KE_IGNORE, 0x03, { KEY_SCROLLLOCK }}, - - /* reported via "video bus" */ - { KE_IGNORE, 0x14, { KEY_BRIGHTNESSUP }}, - { KE_IGNORE, 0x15, { KEY_BRIGHTNESSDOWN }}, - - /* reported in automatic mode when rfkill state changes */ - { KE_SW, 0x1a, {.sw = { SW_RFKILL_ALL, 1 }}}, - { KE_SW, 0x1b, {.sw = { SW_RFKILL_ALL, 0 }}}, - - /* reported via keyboard controller */ - { KE_IGNORE, 0x35, { KEY_MUTE }}, - { KE_IGNORE, 0x36, { KEY_VOLUMEDOWN }}, - { KE_IGNORE, 0x37, { KEY_VOLUMEUP }}, - - /* - * not reported by other means when in manual mode, - * handled automatically when it automatic mode - */ - { KE_KEY, 0xa4, { KEY_RFKILL }}, - { KE_KEY, 0xb1, { KEY_KBDILLUMDOWN }}, - { KE_KEY, 0xb2, { KEY_KBDILLUMUP }}, - { KE_KEY, 0xb8, { KEY_FN_ESC }}, - - { KE_END } - -}; - -/* ========================================================================== */ - -static struct input_dev *qc71_input_dev; - -/* ========================================================================== */ - -static void toggle_fn_lock_from_event_handler(void) -{ - int status = qc71_fn_lock_get_state(); - - if (status < 0) - return; - - /* seemingly the returned status in the WMI event handler is not the current */ - pr_info("setting Fn lock state from %d to %d\n", !status, status); - qc71_fn_lock_set_state(status); -} - -#if IS_ENABLED(CONFIG_LEDS_BRIGHTNESS_HW_CHANGED) -extern struct rw_semaphore leds_list_lock; -extern struct list_head leds_list; - -static void emit_keyboard_led_hw_changed(void) -{ - struct led_classdev *led; - - if (down_read_killable(&leds_list_lock)) - return; - - list_for_each_entry (led, &leds_list, node) { - size_t name_length; - const char *suffix; - - if (!(led->flags & LED_BRIGHT_HW_CHANGED)) - continue; - - name_length = strlen(led->name); - - if (name_length < strlen(KBD_BL_LED_SUFFIX)) - continue; - - suffix = led->name + name_length - strlen(KBD_BL_LED_SUFFIX); - - if (strcmp(suffix, KBD_BL_LED_SUFFIX) == 0) { - if (mutex_lock_interruptible(&led->led_access)) - break; - - if (led_update_brightness(led) >= 0) - led_classdev_notify_brightness_hw_changed(led, led->brightness); - - mutex_unlock(&led->led_access); - break; - } - } - - up_read(&leds_list_lock); -} -#endif - -static void qc71_wmi_event_d2_handler(union acpi_object *obj) -{ - bool do_report = true; - - if (!obj || obj->type != ACPI_TYPE_INTEGER) - return; - - switch (obj->integer.value) { - /* caps lock */ - case 1: - pr_info("caps lock\n"); - break; - - /* num lock */ - case 2: - pr_info("num lock\n"); - break; - - /* scroll lock */ - case 3: - pr_info("scroll lock\n"); - break; - - /* touchpad on */ - case 4: - pr_info("touchpad on\n"); - break; - - /* touchpad off */ - case 5: - pr_info("touchpad off\n"); - break; - - /* increase screen brightness */ - case 20: - pr_info("increase screen brightness\n"); - /* do_report = !acpi_video_handles_brightness_key_presses() */ - break; - - /* decrease screen brightness */ - case 21: - pr_info("decrease screen brightness\n"); - /* do_report = !acpi_video_handles_brightness_key_presses() */ - break; - - /* radio on */ - case 26: - /* triggered in automatic mode when the rfkill hotkey is pressed */ - pr_info("radio on\n"); - break; - - /* radio off */ - case 27: - /* triggered in automatic mode when the rfkill hotkey is pressed */ - pr_info("radio off\n"); - break; - - /* mute/unmute */ - case 53: - pr_info("toggle mute\n"); - break; - - /* decrease volume */ - case 54: - pr_info("decrease volume\n"); - break; - - /* increase volume */ - case 55: - pr_info("increase volume\n"); - break; - - case 57: - pr_info("lightbar on\n"); - break; - - case 58: - pr_info("lightbar off\n"); - break; - - /* enable super key (win key) lock */ - case 64: - pr_info("enable super key lock\n"); - break; - - /* decrease volume */ - case 65: - pr_info("disable super key lock\n"); - break; - - /* enable/disable airplane mode */ - case 164: - pr_info("toggle airplane mode\n"); - break; - - /* super key (win key) lock state changed */ - case 165: - pr_info("super key lock state changed\n"); - sysfs_notify(&qc71_platform_dev->dev.kobj, NULL, "super_key_lock"); - break; - - case 166: - pr_info("lightbar state changed\n"); - break; - - /* fan boost state changed */ - case 167: - pr_info("fan boost state changed\n"); - break; - - /* charger unplugged/plugged in */ - case 171: - pr_info("AC plugged/unplugged\n"); - break; - - /* perf mode button pressed */ - case 176: - pr_info("change perf mode\n"); - /* TODO: should it be handled here? */ - break; - - /* increase keyboard backlight */ - case 177: - pr_info("keyboard backlight decrease\n"); - /* TODO: should it be handled here? */ - break; - - /* decrease keyboard backlight */ - case 178: - pr_info("keyboard backlight increase\n"); - /* TODO: should it be handled here? */ - break; - - /* toggle Fn lock (Fn+ESC)*/ - case 184: - pr_info("toggle Fn lock\n"); - toggle_fn_lock_from_event_handler(); - sysfs_notify(&qc71_platform_dev->dev.kobj, NULL, "fn_lock"); - break; - - /* keyboard backlight brightness changed */ - case 240: - pr_info("keyboard backlight changed\n"); - -#if IS_ENABLED(CONFIG_LEDS_BRIGHTNESS_HW_CHANGED) - emit_keyboard_led_hw_changed(); -#endif - break; - - default: - pr_warn("unknown code: %u\n", (unsigned int) obj->integer.value); - break; - } - - if (do_report && qc71_input_dev) - sparse_keymap_report_event(qc71_input_dev, - obj->integer.value, 1, true); - -} - -static void qc71_wmi_event_handler(u32 value, void *context) -{ - struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *obj; - acpi_status status; - - pr_info("%s(value=%#04x)\n", __func__, (unsigned int) value); - status = wmi_get_event_data(value, &response); - - if (ACPI_FAILURE(status)) { - pr_err("bad WMI event status: %#010x\n", (unsigned int) status); - return; - } - - obj = response.pointer; - - if (obj) { - pr_info("obj->type = %d\n", (int) obj->type); - if (obj->type == ACPI_TYPE_INTEGER) { - pr_info("int = %u\n", (unsigned int) obj->integer.value); - } else if (obj->type == ACPI_TYPE_STRING) { - pr_info("string = '%s'\n", obj->string.pointer); - } else if (obj->type == ACPI_TYPE_BUFFER) { - uint32_t i; - - for (i = 0; i < obj->buffer.length; i++) - pr_info("buf[%u] = %#04x\n", - (unsigned int) i, - (unsigned int) obj->buffer.pointer[i]); - } - } - - switch (value) { - case 0xd2: - qc71_wmi_event_d2_handler(obj); - break; - case 0xd1: - case 0xd0: - break; - } - - kfree(obj); -} - -static int __init setup_input_dev(void) -{ - int err = 0; - - qc71_input_dev = input_allocate_device(); - if (!qc71_input_dev) - return -ENOMEM; - - qc71_input_dev->name = "QC71 laptop input device"; - qc71_input_dev->phys = "qc71_laptop/input0"; - qc71_input_dev->id.bustype = BUS_HOST; - qc71_input_dev->dev.parent = &qc71_platform_dev->dev; - - err = sparse_keymap_setup(qc71_input_dev, qc71_wmi_hotkeys, NULL); - if (err) - goto err_free_device; - - err = qc71_rfkill_get_wifi_state(); - if (err >= 0) - input_report_switch(qc71_input_dev, SW_RFKILL_ALL, err); - else - input_report_switch(qc71_input_dev, SW_RFKILL_ALL, 1); - - err = input_register_device(qc71_input_dev); - if (err) - goto err_free_device; - - return err; - -err_free_device: - input_free_device(qc71_input_dev); - qc71_input_dev = NULL; - - return err; -} - -/* ========================================================================== */ - -int __init qc71_wmi_events_setup(void) -{ - int err = 0, i; - - (void) setup_input_dev(); - - for (i = 0; i < ARRAY_SIZE(qc71_wmi_event_guids); i++) { - const char *guid = qc71_wmi_event_guids[i].guid; - acpi_status status = - wmi_install_notify_handler(guid, qc71_wmi_event_handler, NULL); - - if (ACPI_FAILURE(status)) { - pr_warn("could not install WMI notify handler for '%s': [%#010lx] %s\n", - guid, (unsigned long) status, acpi_format_exception(status)); - } else { - qc71_wmi_event_guids[i].handler_installed = true; - } - } - - return err; -} - -void qc71_wmi_events_cleanup(void) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(qc71_wmi_event_guids); i++) { - if (qc71_wmi_event_guids[i].handler_installed) { - wmi_remove_notify_handler(qc71_wmi_event_guids[i].guid); - qc71_wmi_event_guids[i].handler_installed = false; - } - } - - if (qc71_input_dev) - input_unregister_device(qc71_input_dev); -} diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/events.h slimbook-qc71-0.2/slimbook-qc71-0.0/events.h --- slimbook-qc71-0.1/slimbook-qc71-0.0/events.h 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/events.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef QC71_WMI_EVENTS_H -#define QC71_WMI_EVENTS_H - -#if IS_ENABLED(CONFIG_LEDS_CLASS) - -#include - -int __init qc71_wmi_events_setup(void); -void qc71_wmi_events_cleanup(void); - -#else - -static inline int qc71_wmi_events_setup(void) -{ - return 0; -} - -static inline void qc71_wmi_events_cleanup(void) -{ - -} - -#endif - -#endif /* QC71_WMI_EVENTS_H */ diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/fan.c slimbook-qc71-0.2/slimbook-qc71-0.0/fan.c --- slimbook-qc71-0.1/slimbook-qc71-0.0/fan.c 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/fan.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,212 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "pr.h" - -#include - -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0) -static inline int fixp_linear_interpolate(int x0, int y0, int x1, int y1, int x) -{ - if (y0 == y1 || x == x0) - return y0; - if (x1 == x0 || x == x1) - return y1; - - return y0 + ((y1 - y0) * (x - x0) / (x1 - x0)); -} -#else -#include /* fixp-arith.h needs it, but doesn't include it */ -#include -#endif - -#include -#include -#include - -#include "ec.h" -#include "fan.h" -#include "util.h" - -/* ========================================================================== */ - -static const uint16_t qc71_fan_rpm_addrs[] = { - FAN_RPM_1_ADDR, - FAN_RPM_2_ADDR, -}; - -static const uint16_t qc71_fan_pwm_addrs[] = { - FAN_PWM_1_ADDR, - FAN_PWM_2_ADDR, -}; - -static const uint16_t qc71_fan_temp_addrs[] = { - FAN_TEMP_1_ADDR, - FAN_TEMP_2_ADDR, -}; - -/* ========================================================================== */ - -static DEFINE_MUTEX(fan_lock); - -/* ========================================================================== */ - -static int qc71_fan_get_status(void) -{ - return ec_read_byte(FAN_CTRL_ADDR); -} - -/* 'fan_lock' must be held */ -static int qc71_fan_get_mode_unlocked(void) -{ - int err; - - lockdep_assert_held(&fan_lock); - - err = ec_read_byte(CTRL_1_ADDR); - if (err < 0) - return err; - - if (err & CTRL_1_MANUAL_MODE) { - err = qc71_fan_get_status(); - if (err < 0) - return err; - - if (err & FAN_CTRL_FAN_BOOST) { - err = qc71_fan_get_pwm(0); - - if (err < 0) - return err; - - if (err == FAN_MAX_PWM) - err = 0; /* disengaged */ - else - err = 1; /* manual */ - - } else if (err & FAN_CTRL_AUTO) { - err = 2; /* automatic fan control */ - } else { - err = 1; /* manual */ - } - } else { - err = 2; /* automatic fan control */ - } - - return err; -} - -/* ========================================================================== */ - -int qc71_fan_get_rpm(uint8_t fan_index) -{ - union qc71_ec_result res; - int err; - - if (fan_index >= ARRAY_SIZE(qc71_fan_rpm_addrs)) - return -EINVAL; - - err = qc71_ec_read(qc71_fan_rpm_addrs[fan_index], &res); - - if (err) - return err; - - return res.bytes.b1 << 8 | res.bytes.b2; -} - -int qc71_fan_query_abnorm(void) -{ - int res = ec_read_byte(CTRL_1_ADDR); - - if (res < 0) - return res; - - return !!(res & CTRL_1_FAN_ABNORMAL); -} - -int qc71_fan_get_pwm(uint8_t fan_index) -{ - int err; - - if (fan_index >= ARRAY_SIZE(qc71_fan_pwm_addrs)) - return -EINVAL; - - err = ec_read_byte(qc71_fan_pwm_addrs[fan_index]); - if (err < 0) - return err; - - return fixp_linear_interpolate(0, 0, FAN_MAX_PWM, U8_MAX, err); -} - -int qc71_fan_set_pwm(uint8_t fan_index, uint8_t pwm) -{ - if (fan_index >= ARRAY_SIZE(qc71_fan_pwm_addrs)) - return -EINVAL; - - return ec_write_byte(qc71_fan_pwm_addrs[fan_index], - fixp_linear_interpolate(0, 0, - U8_MAX, FAN_MAX_PWM, - pwm)); -} - -int qc71_fan_get_temp(uint8_t fan_index) -{ - if (fan_index >= ARRAY_SIZE(qc71_fan_temp_addrs)) - return -EINVAL; - - return ec_read_byte(qc71_fan_temp_addrs[fan_index]); -} - -int qc71_fan_get_mode(void) -{ - int err = mutex_lock_interruptible(&fan_lock); - - if (err) - return err; - - err = qc71_fan_get_mode_unlocked(); - - mutex_unlock(&fan_lock); - return err; -} - -int qc71_fan_set_mode(uint8_t mode) -{ - int err, oldpwm; - - err = mutex_lock_interruptible(&fan_lock); - if (err) - return err; - - switch (mode) { - case 0: - err = ec_write_byte(FAN_CTRL_ADDR, FAN_CTRL_FAN_BOOST); - if (err) - goto out; - - err = qc71_fan_set_pwm(0, FAN_MAX_PWM); - break; - case 1: - oldpwm = err = qc71_fan_get_pwm(0); - if (err < 0) - goto out; - - err = ec_write_byte(FAN_CTRL_ADDR, FAN_CTRL_FAN_BOOST); - if (err < 0) - goto out; - - err = qc71_fan_set_pwm(0, oldpwm); - if (err < 0) - (void) ec_write_byte(FAN_CTRL_ADDR, 0x80 | FAN_CTRL_AUTO); - /* try to restore automatic fan control */ - - break; - case 2: - err = ec_write_byte(FAN_CTRL_ADDR, 0x80 | FAN_CTRL_AUTO); - break; - default: - err = -EINVAL; - break; - } - -out: - mutex_unlock(&fan_lock); - return err; -} diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/fan.h slimbook-qc71-0.2/slimbook-qc71-0.0/fan.h --- slimbook-qc71-0.1/slimbook-qc71-0.0/fan.h 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/fan.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,22 +0,0 @@ -#ifndef QC71_LAPTOP_FAN_H -#define QC71_LAPTOP_FAN_H - -#include - -/* ========================================================================== */ - -#define FAN_MAX_PWM 200 -#define FAN_CTRL_MAX_LEVEL 7 -#define FAN_CTRL_LEVEL(level) (128 + (level)) - -/* ========================================================================== */ - -int qc71_fan_get_rpm(uint8_t fan_index); -int qc71_fan_query_abnorm(void); -int qc71_fan_get_pwm(uint8_t fan_index); -int qc71_fan_set_pwm(uint8_t fan_index, uint8_t pwm); -int qc71_fan_get_temp(uint8_t fan_index); -int qc71_fan_get_mode(void); -int qc71_fan_set_mode(uint8_t mode); - -#endif /* QC71_LAPTOP_FAN_H */ diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/features.c slimbook-qc71-0.2/slimbook-qc71-0.0/features.c --- slimbook-qc71-0.1/slimbook-qc71-0.0/features.c 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/features.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,233 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "pr.h" - -#include -#include -#include -#include -#include -#include - -#include "ec.h" -#include "features.h" - -/* ========================================================================== */ - -struct oem_string_walker_data { - char *value; - int index; -}; - -/* ========================================================================== */ - -static int __init slimbook_dmi_cb(const struct dmi_system_id *id) -{ - qc71_features.fn_lock = true; - qc71_features.silent_mode = true; - - return 1; -} - -static const struct dmi_system_id qc71_dmi_table[] __initconst = { - { - .matches = { - DMI_MATCH(DMI_BOARD_NAME, "LAPQC71"), - { } - } - }, - { - /* https://avell.com.br/avell-a60-muv-295765 */ - .matches = { - DMI_EXACT_MATCH(DMI_CHASSIS_VENDOR, "Avell High Performance"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "A60 MUV"), - { } - } - }, - { - /* Slimbook PROX AMD */ - .callback = slimbook_dmi_cb, - .matches = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "PROX-AMD"), - { } - } - }, - { - /* Slimbook PROX15 AMD */ - .callback = slimbook_dmi_cb, - .matches = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "PROX15-AMD"), - { } - } - }, - { - /* Slimbook PROX AMD5 */ - .callback = slimbook_dmi_cb, - .matches = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME,"PROX-AMD5"), - { } - } - }, - { - /* Slimbook PROX15 AMD5 */ - .callback = slimbook_dmi_cb, - .matches = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME,"PROX15-AMD5"), - { } - } - }, - { } -}; - -/* ========================================================================== */ - -struct qc71_features_struct qc71_features; - -/* ========================================================================== */ - -static void __init oem_string_walker(const struct dmi_header *dm, void *ptr) -{ - int i, count; - const uint8_t *s; - struct oem_string_walker_data *data = ptr; - - if (dm->type != 11 || dm->length < 5 || !IS_ERR_OR_NULL(data->value)) - return; - - count = *(uint8_t *)(dm + 1); - - if (data->index >= count) - return; - - i = 0; - s = ((uint8_t *)dm) + dm->length; - - while (i++ < data->index && *s) - s += strlen(s) + 1; - - data->value = kstrdup(s, GFP_KERNEL); - - if (!data->value) - data->value = ERR_PTR(-ENOMEM); -} - -static char * __init read_oem_string(int index) -{ - struct oem_string_walker_data d = {.value = ERR_PTR(-ENOENT), - .index = index}; - int err = dmi_walk(oem_string_walker, &d); - - if (err) { - if (!IS_ERR_OR_NULL(d.value)) - kfree(d.value); - return ERR_PTR(err); - } - - return d.value; -} - -/* QCCFL357.0062.2020.0313.1530 -> 62 */ -static int __pure __init parse_bios_version(const char *str) -{ - const char *p = strchr(str, '.'), *p2; - int bios_version; - - if (!p) - return -EINVAL; - - p2 = strchr(p + 1, '.'); - - if (!p2) - return -EINVAL; - - p += 1; - - bios_version = 0; - - while (p != p2) { - if (!isdigit(*p)) - return -EINVAL; - - bios_version = 10 * bios_version + *p - '0'; - p += 1; - } - - return bios_version; -} - -static int __init check_features_ec(void) -{ - int err = ec_read_byte(SUPPORT_1_ADDR); - - if (err >= 0) { - qc71_features.super_key_lock = !!(err & SUPPORT_1_SUPER_KEY_LOCK); - qc71_features.lightbar = !!(err & SUPPORT_1_LIGHTBAR); - qc71_features.fan_boost = !!(err & SUPPORT_1_FAN_BOOST); - } else { - pr_warn("failed to query support_1 byte: %d\n", err); - } - - return err; -} - -static int __init check_features_bios(void) -{ - const char *bios_version_str; - int bios_version; - - if (!dmi_check_system(qc71_dmi_table)) { - pr_warn("no DMI match\n"); - return -ENODEV; - } - - bios_version_str = dmi_get_system_info(DMI_BIOS_VERSION); - - if (!bios_version_str) { - pr_warn("failed to get BIOS version DMI string\n"); - return -ENOENT; - } - - pr_info("BIOS version string: '%s'\n", bios_version_str); - - bios_version = parse_bios_version(bios_version_str); - - if (bios_version < 0) { - pr_warn("cannot parse BIOS version\n"); - return -EINVAL; - } - - pr_info("BIOS version: %04d\n", bios_version); - - if (bios_version >= 114) { - const char *s = read_oem_string(18); - size_t s_len; - - if (IS_ERR(s)) - return PTR_ERR(s); - - s_len = strlen(s); - - pr_info("OEM_STRING(18) = '%s'\n", s); - - /* if it is entirely spaces */ - if (strspn(s, " ") == s_len) { - qc71_features.fn_lock = true; - qc71_features.batt_charge_limit = true; - qc71_features.fan_extras = true; - } else if (s_len > 0) { - /* TODO */ - pr_warn("cannot extract supported features"); - } - - kfree(s); - } - - return 0; -} - -int __init qc71_check_features(void) -{ - (void) check_features_ec(); - (void) check_features_bios(); - - return 0; -} diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/features.h slimbook-qc71-0.2/slimbook-qc71-0.0/features.h --- slimbook-qc71-0.1/slimbook-qc71-0.0/features.h 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/features.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef QC71_FEATURES_H -#define QC71_FEATURES_H - -#include -#include - -struct qc71_features_struct { - bool super_key_lock : 1; - bool lightbar : 1; - bool fan_boost : 1; - bool fn_lock : 1; - bool batt_charge_limit : 1; - bool fan_extras : 1; /* duty cycle reduction, always on mode */ - bool silent_mode : 1; /* Slimbook silent mode: decreases fan rpm limit and tdp */ -}; - -/* ========================================================================== */ - -extern struct qc71_features_struct qc71_features; - -/* ========================================================================== */ - -int __init qc71_check_features(void); - -#endif /* QC71_FEATURES_H */ diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/hwmon.c slimbook-qc71-0.2/slimbook-qc71-0.0/hwmon.c --- slimbook-qc71-0.1/slimbook-qc71-0.0/hwmon.c 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/hwmon.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "pr.h" - -#include -#include -#include -#include - -#include "hwmon_fan.h" -#include "hwmon_pwm.h" - -/* ========================================================================== */ - -static bool nohwmon; -module_param(nohwmon, bool, 0444); -MODULE_PARM_DESC(nohwmon, "do not report to the hardware monitoring subsystem (default=false)"); - -/* ========================================================================== */ - -int __init qc71_hwmon_setup(void) -{ - if (nohwmon) - return -ENODEV; - - (void) qc71_hwmon_fan_setup(); - (void) qc71_hwmon_pwm_setup(); - - return 0; -} - -void qc71_hwmon_cleanup(void) -{ - (void) qc71_hwmon_fan_cleanup(); - (void) qc71_hwmon_pwm_cleanup(); -} diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/hwmon_fan.c slimbook-qc71-0.2/slimbook-qc71-0.0/hwmon_fan.c --- slimbook-qc71-0.1/slimbook-qc71-0.0/hwmon_fan.c 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/hwmon_fan.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,153 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "pr.h" - -#include -#include -#include -#include -#include -#include - -#include "ec.h" -#include "fan.h" -#include "features.h" -#include "pdev.h" - -/* ========================================================================== */ - -static struct device *qc71_hwmon_fan_dev; - -/* ========================================================================== */ - -static umode_t qc71_hwmon_fan_is_visible(const void *data, enum hwmon_sensor_types type, - u32 attr, int channel) -{ - switch (type) { - case hwmon_fan: - switch (attr) { - case hwmon_fan_input: - case hwmon_fan_fault: - return 0444; - } - break; - case hwmon_temp: - switch (attr) { - case hwmon_temp_input: - case hwmon_temp_label: - return 0444; - } - default: - break; - } - - return 0; -} - -static int qc71_hwmon_fan_read(struct device *device, enum hwmon_sensor_types type, - u32 attr, int channel, long *value) -{ - int err; - - switch (type) { - case hwmon_fan: - switch (attr) { - case hwmon_fan_input: - err = qc71_fan_get_rpm(channel); - if (err < 0) - return err; - - *value = err; - break; - default: - return -EOPNOTSUPP; - } - break; - case hwmon_temp: - switch (attr) { - case hwmon_temp_input: - err = qc71_fan_get_temp(channel); - if (err < 0) - return err; - - *value = err * 1000; - break; - default: - return -EOPNOTSUPP; - } - break; - default: - return -EOPNOTSUPP; - } - - return 0; -} - -static int qc71_hwmon_fan_read_string(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int channel, const char **str) -{ - static const char * const temp_labels[] = { - "fan1_temp", - "fan2_temp", - }; - - switch (type) { - case hwmon_temp: - switch (attr) { - case hwmon_temp_label: - *str = temp_labels[channel]; - break; - default: - return -EOPNOTSUPP; - } - break; - default: - return -EOPNOTSUPP; - } - - return 0; -} - -/* ========================================================================== */ - -static const struct hwmon_channel_info *qc71_hwmon_fan_ch_info[] = { - HWMON_CHANNEL_INFO(fan, - HWMON_F_INPUT, - HWMON_F_INPUT), - HWMON_CHANNEL_INFO(temp, - HWMON_T_INPUT | HWMON_T_LABEL, - HWMON_T_INPUT | HWMON_T_LABEL), - NULL -}; - -static const struct hwmon_ops qc71_hwmon_fan_ops = { - .is_visible = qc71_hwmon_fan_is_visible, - .read = qc71_hwmon_fan_read, - .read_string = qc71_hwmon_fan_read_string, -}; - -static const struct hwmon_chip_info qc71_hwmon_fan_chip_info = { - .ops = &qc71_hwmon_fan_ops, - .info = qc71_hwmon_fan_ch_info, -}; - -/* ========================================================================== */ - -int __init qc71_hwmon_fan_setup(void) -{ - int err = 0; - - qc71_hwmon_fan_dev = hwmon_device_register_with_info( - &qc71_platform_dev->dev, KBUILD_MODNAME ".hwmon.fan", NULL, - &qc71_hwmon_fan_chip_info, NULL); - - if (IS_ERR(qc71_hwmon_fan_dev)) - err = PTR_ERR(qc71_hwmon_fan_dev); - - return err; -} - -void qc71_hwmon_fan_cleanup(void) -{ - if (!IS_ERR_OR_NULL(qc71_hwmon_fan_dev)) - hwmon_device_unregister(qc71_hwmon_fan_dev); -} diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/hwmon_fan.h slimbook-qc71-0.2/slimbook-qc71-0.0/hwmon_fan.h --- slimbook-qc71-0.1/slimbook-qc71-0.0/hwmon_fan.h 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/hwmon_fan.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef QC71_HWMON_FAN_H -#define QC71_HWMON_FAN_H - -#include - -int __init qc71_hwmon_fan_setup(void); -void qc71_hwmon_fan_cleanup(void); - -#endif /* QC71_HWMON_FAN_H */ diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/hwmon.h slimbook-qc71-0.2/slimbook-qc71-0.0/hwmon.h --- slimbook-qc71-0.1/slimbook-qc71-0.0/hwmon.h 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/hwmon.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef QC71_HWMON_H -#define QC71_HWMON_H - -#if IS_ENABLED(CONFIG_HWMON) - -#include - -int __init qc71_hwmon_setup(void); -void qc71_hwmon_cleanup(void); - -#else - -static inline int qc71_hwmon_setup(void) -{ - return 0; -} - -static inline void qc71_hwmon_cleanup(void) -{ - -} - -#endif - -#endif /* QC71_HWMON_H */ diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/hwmon_pwm.c slimbook-qc71-0.2/slimbook-qc71-0.0/hwmon_pwm.c --- slimbook-qc71-0.1/slimbook-qc71-0.0/hwmon_pwm.c 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/hwmon_pwm.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "pr.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "fan.h" -#include "features.h" -#include "pdev.h" -#include "util.h" - -/* ========================================================================== */ - -static struct device *qc71_hwmon_pwm_dev; - -/* ========================================================================== */ - -static umode_t qc71_hwmon_pwm_is_visible(const void *data, enum hwmon_sensor_types type, - u32 attr, int channel) -{ - if (type != hwmon_pwm && attr != hwmon_pwm_enable) - return -EOPNOTSUPP; - - return 0644; -} - -static int qc71_hwmon_pwm_read(struct device *device, enum hwmon_sensor_types type, - u32 attr, int channel, long *value) -{ - int err; - - switch (type) { - case hwmon_pwm: - switch (attr) { - case hwmon_pwm_enable: - err = qc71_fan_get_mode(); - if (err < 0) - return err; - - *value = err; - break; - case hwmon_pwm_input: - err = qc71_fan_get_pwm(channel); - if (err < 0) - return err; - - *value = err; - break; - default: - return -EOPNOTSUPP; - } - break; - default: - return -EOPNOTSUPP; - } - - return 0; -} - -static int qc71_hwmon_pwm_write(struct device *device, enum hwmon_sensor_types type, - u32 attr, int channel, long value) -{ - switch (type) { - case hwmon_pwm: - switch (attr) { - case hwmon_pwm_enable: - return qc71_fan_set_mode(value); - case hwmon_pwm_input: - return qc71_fan_set_pwm(channel, value); - default: - return -EOPNOTSUPP; - } - default: - return -EOPNOTSUPP; - } - - return 0; -} - -static const struct hwmon_channel_info *qc71_hwmon_pwm_ch_info[] = { - HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE), - HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT, HWMON_PWM_INPUT), - NULL -}; - -static const struct hwmon_ops qc71_hwmon_pwm_ops = { - .is_visible = qc71_hwmon_pwm_is_visible, - .read = qc71_hwmon_pwm_read, - .write = qc71_hwmon_pwm_write, -}; - -static const struct hwmon_chip_info qc71_hwmon_pwm_chip_info = { - .ops = &qc71_hwmon_pwm_ops, - .info = qc71_hwmon_pwm_ch_info, -}; - -/* ========================================================================== */ - -int __init qc71_hwmon_pwm_setup(void) -{ - int err = 0; - - if (!qc71_features.fan_boost) - return -ENODEV; - - qc71_hwmon_pwm_dev = hwmon_device_register_with_info( - &qc71_platform_dev->dev, KBUILD_MODNAME ".hwmon.pwm", NULL, - &qc71_hwmon_pwm_chip_info, NULL); - - if (IS_ERR(qc71_hwmon_pwm_dev)) - err = PTR_ERR(qc71_hwmon_pwm_dev); - - return err; -} - -void qc71_hwmon_pwm_cleanup(void) -{ - if (!IS_ERR_OR_NULL(qc71_hwmon_pwm_dev)) - hwmon_device_unregister(qc71_hwmon_pwm_dev); -} diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/hwmon_pwm.h slimbook-qc71-0.2/slimbook-qc71-0.0/hwmon_pwm.h --- slimbook-qc71-0.1/slimbook-qc71-0.0/hwmon_pwm.h 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/hwmon_pwm.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef QC71_HWMON_PWM_H -#define QC71_HWMON_PWM_H - -#include - -int __init qc71_hwmon_pwm_setup(void); -void qc71_hwmon_pwm_cleanup(void); - -#endif /* QC71_HWMON_PWM_H */ diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/led_lightbar.c slimbook-qc71-0.2/slimbook-qc71-0.0/led_lightbar.c --- slimbook-qc71-0.1/slimbook-qc71-0.0/led_lightbar.c 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/led_lightbar.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,404 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "pr.h" - -#include -/* #include */ -#include -#include -#include - -#include "util.h" -#include "ec.h" -#include "features.h" -#include "led_lightbar.h" -#include "pdev.h" - -/* ========================================================================== */ - -#if IS_ENABLED(CONFIG_LEDS_CLASS) - -enum qc71_lightbar_color { - LIGHTBAR_RED = 0, - LIGHTBAR_GREEN = 1, - LIGHTBAR_BLUE = 2, - LIGHTBAR_COLOR_COUNT -}; - -static const uint16_t lightbar_color_addrs[LIGHTBAR_COLOR_COUNT] = { - [LIGHTBAR_RED] = LIGHTBAR_RED_ADDR, - [LIGHTBAR_GREEN] = LIGHTBAR_GREEN_ADDR, - [LIGHTBAR_BLUE] = LIGHTBAR_BLUE_ADDR, -}; - -static const uint8_t lightbar_colors[LIGHTBAR_COLOR_COUNT] = { - LIGHTBAR_RED, - LIGHTBAR_GREEN, - LIGHTBAR_BLUE, -}; - -/* f(x) = 4x */ -static const uint8_t lightbar_color_values[LIGHTBAR_COLOR_COUNT][10] = { - [LIGHTBAR_RED] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36}, - [LIGHTBAR_GREEN] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36}, - [LIGHTBAR_BLUE] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36}, -}; - -/* inverse of 'lightbar_color_values' */ -static const uint8_t lightbar_pwm_to_level[LIGHTBAR_COLOR_COUNT][256] = { - [LIGHTBAR_RED] = { - [0] = 0, - [4] = 1, - [8] = 2, - [12] = 3, - [16] = 4, - [20] = 5, - [24] = 6, - [28] = 7, - [32] = 8, - [36] = 9, - }, - - [LIGHTBAR_GREEN] = { - [0] = 0, - [4] = 1, - [8] = 2, - [12] = 3, - [16] = 4, - [20] = 5, - [24] = 6, - [28] = 7, - [32] = 8, - [36] = 9, - }, - - [LIGHTBAR_BLUE] = { - [0] = 0, - [4] = 1, - [8] = 2, - [12] = 3, - [16] = 4, - [20] = 5, - [24] = 6, - [28] = 7, - [32] = 8, - [36] = 9, - }, -}; - - -/* ========================================================================== */ - -static bool nolightbar; -module_param(nolightbar, bool, 0444); -MODULE_PARM_DESC(nolightbar, "do not register the lightbar to the leds subsystem (default=false)"); - -static bool lightbar_led_registered; - -/* ========================================================================== */ - -static inline int qc71_lightbar_get_status(void) -{ - return ec_read_byte(LIGHTBAR_CTRL_ADDR); -} - -static inline int qc71_lightbar_write_ctrl(uint8_t ctrl) -{ - return ec_write_byte(LIGHTBAR_CTRL_ADDR, ctrl); -} - -/* ========================================================================== */ - -static int qc71_lightbar_switch(uint8_t mask, bool on) -{ - int status; - - if (mask != LIGHTBAR_CTRL_S0_OFF && mask != LIGHTBAR_CTRL_S3_OFF) - return -EINVAL; - - status = qc71_lightbar_get_status(); - - if (status < 0) - return status; - - return qc71_lightbar_write_ctrl(SET_BIT(status, mask, !on)); -} - -static int qc71_lightbar_set_color_level(uint8_t color, uint8_t level) -{ - if (color >= ARRAY_SIZE(lightbar_color_addrs)) - return -EINVAL; - - if (level >= ARRAY_SIZE(lightbar_color_values[color])) - return -EINVAL; - - return ec_write_byte(lightbar_color_addrs[color], lightbar_color_values[color][level]); -} - -static int qc71_lightbar_get_color_level(uint8_t color) -{ - int err; - - if (color >= ARRAY_SIZE(lightbar_color_addrs)) - return -EINVAL; - - err = ec_read_byte(lightbar_color_addrs[color]); - if (err < 0) - return err; - - return lightbar_pwm_to_level[color][err]; -} - -static int qc71_lightbar_set_rainbow_mode(bool on) -{ - int status = qc71_lightbar_get_status(); - - if (status < 0) - return status; - - return qc71_lightbar_write_ctrl(SET_BIT(status, LIGHTBAR_CTRL_RAINBOW, on)); -} - -static int qc71_lightbar_set_color(unsigned int color) -{ - int err = 0, i; - - if (color > 999) /* color must lie in [0, 999] */ - return -EINVAL; - - for (i = ARRAY_SIZE(lightbar_colors) - 1; i >= 0 && !err; i--) - err = qc71_lightbar_set_color_level(lightbar_colors[i], - do_div(color, 10)); - - return err; -} - -/* ========================================================================== */ -/* lightbar attrs */ - -static ssize_t lightbar_s3_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int value = qc71_lightbar_get_status(); - - if (value < 0) - return value; - - return sprintf(buf, "%d\n", !(value & LIGHTBAR_CTRL_S3_OFF)); -} - -static ssize_t lightbar_s3_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - int err; - bool value; - - if (kstrtobool(buf, &value)) - return -EINVAL; - - err = qc71_lightbar_switch(LIGHTBAR_CTRL_S3_OFF, value); - - if (err) - return err; - - return count; -} - -static ssize_t lightbar_color_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned int color = 0; - size_t i; - - for (i = 0; i < ARRAY_SIZE(lightbar_colors); i++) { - int level = qc71_lightbar_get_color_level(lightbar_colors[i]); - - if (level < 0) - return level; - - color *= 10; - - if (0 <= level && level <= 9) - color += level; - } - - return sprintf(buf, "%03u\n", color); -} - -static ssize_t lightbar_color_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - unsigned int value; - int err; - - if (kstrtouint(buf, 10, &value)) - return -EINVAL; - - err = qc71_lightbar_set_color(value); - if (err) - return err; - - return count; -} - -static ssize_t lightbar_rainbow_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int status = qc71_lightbar_get_status(); - - if (status < 0) - return status; - - return sprintf(buf, "%d\n", !!(status & LIGHTBAR_CTRL_RAINBOW)); -} - -static ssize_t lightbar_rainbow_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - int err; - bool value; - - if (kstrtobool(buf, &value)) - return -EINVAL; - - err = qc71_lightbar_set_rainbow_mode(value); - - if (err) - return err; - - return count; -} - -static enum led_brightness qc71_lightbar_led_get_brightness(struct led_classdev *led_cdev) -{ - int err = qc71_lightbar_get_status(); - - if (err) - return 0; - - return !(err & LIGHTBAR_CTRL_S0_OFF); -} - -static int qc71_lightbar_led_set_brightness(struct led_classdev *led_cdev, - enum led_brightness value) -{ - return qc71_lightbar_switch(LIGHTBAR_CTRL_S0_OFF, !!value); -} - -#if 0 -static int qc71_lightbar_led_set_brightness(struct led_classdev *led_cdev, - enum led_brightness brightness) -{ - struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); - unsigned int color = 0, i; - int err; - - led_mc_calc_color_components(led_mc_cdev, brightness); - - for (i = 0; i < led_mc_cdev->num_colors; i++) { - if (led_mc_cdev->subled_info[i].brightness > 9) - return -EINVAL; - - color = 10 * color + led_mc_cdev->subled_info[i].brightness; - } - - if (color) { - err = qc71_lightbar_switch(LIGHTBAR_CTRL_S0_OFF, 1); - - if (err) - goto out; - - err = qc71_lightbar_set_color(color); - } else { - err = qc71_lightbar_switch(LIGHTBAR_CTRL_S0_OFF, 0); - } - -out: - return err; -} -#endif - -/* ========================================================================== */ - -static DEVICE_ATTR(brightness_s3, 0644, lightbar_s3_show, lightbar_s3_store); -static DEVICE_ATTR(color, 0644, lightbar_color_show, lightbar_color_store); -static DEVICE_ATTR(rainbow_mode, 0644, lightbar_rainbow_show, lightbar_rainbow_store); - -static struct attribute *qc71_lightbar_led_attrs[] = { - &dev_attr_brightness_s3.attr, - &dev_attr_color.attr, - &dev_attr_rainbow_mode.attr, - NULL -}; - -ATTRIBUTE_GROUPS(qc71_lightbar_led); - -static struct led_classdev qc71_lightbar_led = { - .name = KBUILD_MODNAME "::lightbar", - .max_brightness = 1, - .brightness_get = qc71_lightbar_led_get_brightness, - .brightness_set_blocking = qc71_lightbar_led_set_brightness, - .groups = qc71_lightbar_led_groups, -}; - -#if 0 -static struct mc_subled qc71_lightbar_subleds[LIGHTBAR_COLOR_COUNT] = { - [LIGHTBAR_RED] = { - .color_index = LED_COLOR_ID_RED, - }, - [LIGHTBAR_GREEN] = { - .color_index = LED_COLOR_ID_GREEN, - }, - [LIGHTBAR_BLUE] = { - .color_index = LED_COLOR_ID_BLUE, - }, -}; - -static struct led_classdev_mc qc71_lightbar_led = { - .num_colors = ARRAY_SIZE(qc71_lightbar_subleds), - .subled_info = qc71_lightbar_subleds, - .led_cdev = { - .name = KBUILD_MODNAME "::lightbar", - .max_brightness = 9, - .brightness_set_blocking = qc71_lightbar_led_set_brightness, - }, -}; -#endif - -/* ========================================================================== */ - -int __init qc71_led_lightbar_setup(void) -{ - int err; - - if (nolightbar || !qc71_features.lightbar) - return -ENODEV; - -#if 0 - err = led_classdev_multicolor_register(&qc71_platform_dev->dev, &qc71_lightbar_led); -#endif - err = led_classdev_register(&qc71_platform_dev->dev, &qc71_lightbar_led); - - if (!err) - lightbar_led_registered = true; - -#if 0 - err = device_add_groups(qc71_lightbar_led.led_cdev.dev, qc71_lightbar_led_groups); - if (err) - led_classdev_multicolor_unregister(&qc71_lightbar_led); -#endif - - return err; -} - -void qc71_led_lightbar_cleanup(void) -{ - if (lightbar_led_registered) { -#if 0 - device_remove_groups(qc71_lightbar_led.led_cdev.dev, qc71_lightbar_led_groups); - led_classdev_multicolor_unregister(&qc71_lightbar_led); -#endif - led_classdev_unregister(&qc71_lightbar_led); - } -} - -#endif diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/led_lightbar.h slimbook-qc71-0.2/slimbook-qc71-0.0/led_lightbar.h --- slimbook-qc71-0.1/slimbook-qc71-0.0/led_lightbar.h 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/led_lightbar.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef QC71_LED_LIGHTBAR_H -#define QC71_LED_LIGHTBAR_H - -#if IS_ENABLED(CONFIG_LEDS_CLASS) - -#include - -int __init qc71_led_lightbar_setup(void); -void qc71_led_lightbar_cleanup(void); - -#else - -static inline int qc71_led_lightbar_setup(void) -{ - return 0; -} - -static inline void qc71_led_lightbar_cleanup(void) -{ - -} - -#endif - -#endif /* QC71_LED_LIGHTBAR_H */ diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/LICENSE slimbook-qc71-0.2/slimbook-qc71-0.0/LICENSE --- slimbook-qc71-0.1/slimbook-qc71-0.0/LICENSE 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/LICENSE 1970-01-01 00:00:00.000000000 +0000 @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/main.c slimbook-qc71-0.2/slimbook-qc71-0.0/main.c --- slimbook-qc71-0.1/slimbook-qc71-0.0/main.c 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/main.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,152 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* ========================================================================== */ -/* https://www.intel.com/content/dam/support/us/en/documents/laptops/whitebook/QC71_PROD_SPEC.pdf - * - * - * based on the following resources: - * - https://lwn.net/Articles/391230/ - * - http://blog.nietrzeba.pl/2011/12/mof-decompilation.html - * - https://github.com/tuxedocomputers/tuxedo-cc-wmi/ - * - https://github.com/tuxedocomputers/tuxedo-keyboard/ - * - Control Center for Microsoft Windows - * - http://forum.notebookreview.com/threads/tongfang-gk7cn6s-gk7cp0s-gk7cp7s.825461/page-54 - */ -/* ========================================================================== */ -#include "pr.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "ec.h" -#include "features.h" -#include "wmi.h" - -/* submodules */ -#include "pdev.h" -#include "events.h" -#include "hwmon.h" -#include "battery.h" -#include "led_lightbar.h" -#include "debugfs.h" - -/* ========================================================================== */ - -#define SUBMODULE_ENTRY(_name, _req) { .name = #_name, .init = qc71_ ## _name ## _setup, .cleanup = qc71_ ## _name ## _cleanup, .required = _req } - -static struct qc71_submodule { - const char *name; - - bool required : 1, - initialized : 1; - - int (*init)(void); - void (*cleanup)(void); -} qc71_submodules[] __refdata = { - SUBMODULE_ENTRY(pdev, true), /* must be first */ - SUBMODULE_ENTRY(wmi_events, false), - SUBMODULE_ENTRY(hwmon, false), - SUBMODULE_ENTRY(battery, false), - SUBMODULE_ENTRY(led_lightbar, false), - SUBMODULE_ENTRY(debugfs, false), -}; - -#undef SUBMODULE_ENTRY - -static void do_cleanup(void) -{ - int i; - - for (i = ARRAY_SIZE(qc71_submodules) - 1; i >= 0; i--) { - const struct qc71_submodule *sm = &qc71_submodules[i]; - - if (sm->initialized) - sm->cleanup(); - } -} - -static int __init qc71_laptop_module_init(void) -{ - int err = 0, i; - - if (!wmi_has_guid(QC71_WMI_WMBC_GUID)) { - pr_err("WMI GUID not found\n"); - err = -ENODEV; goto out; - } - - err = ec_read_byte(PROJ_ID_ADDR); - if (err < 0) { - pr_err("failed to query project id: %d\n", err); - goto out; - } - - pr_info("project id: %d\n", err); - - err = ec_read_byte(PLATFORM_ID_ADDR); - if (err < 0) { - pr_err("failed to query platform id: %d\n", err); - goto out; - } - - pr_info("platform id: %d\n", err); - - err = qc71_check_features(); - if (err) { - pr_err("cannot check system features: %d\n", err); - goto out; - } - - pr_info("supported features:"); - if (qc71_features.super_key_lock) pr_cont(" super-key-lock"); - if (qc71_features.lightbar) pr_cont(" lightbar"); - if (qc71_features.fan_boost) pr_cont(" fan-boost"); - if (qc71_features.fn_lock) pr_cont(" fn-lock"); - if (qc71_features.batt_charge_limit) pr_cont(" charge-limit"); - if (qc71_features.fan_extras) pr_cont(" fan-extras"); - if (qc71_features.silent_mode) pr_cont(" silent-mode"); - - pr_cont("\n"); - - for (i = 0; i < ARRAY_SIZE(qc71_submodules); i++) { - struct qc71_submodule *sm = &qc71_submodules[i]; - - err = sm->init(); - if (err) { - pr_warn("failed to initialize %s submodule: %d\n", sm->name, err); - if (sm->required) - goto out; - } else { - sm->initialized = true; - } - } - - err = 0; - -out: - if (err) - do_cleanup(); - else - pr_info("module loaded\n"); - - return err; -} - -static void __exit qc71_laptop_module_cleanup(void) -{ - do_cleanup(); - pr_info("module unloaded\n"); -} - -/* ========================================================================== */ - -module_init(qc71_laptop_module_init); -module_exit(qc71_laptop_module_cleanup); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Barnabás Pőcze "); -MODULE_DESCRIPTION("QC71 laptop platform driver"); -MODULE_ALIAS("wmi:" QC71_WMI_WMBC_GUID); diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/Makefile slimbook-qc71-0.2/slimbook-qc71-0.0/Makefile --- slimbook-qc71-0.1/slimbook-qc71-0.0/Makefile 2022-08-17 10:06:50.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/Makefile 1970-01-01 00:00:00.000000000 +0000 @@ -1,41 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -MODNAME = qc71_laptop -MODVER = 0.0 - -obj-m += $(MODNAME).o - -# alphabetically sorted -$(MODNAME)-y += ec.o \ - features.o \ - main.o \ - misc.o \ - pdev.o \ - events.o \ - -$(MODNAME)-$(CONFIG_DEBUG_FS) += debugfs.o -$(MODNAME)-$(CONFIG_ACPI_BATTERY) += battery.o -$(MODNAME)-$(CONFIG_LEDS_CLASS) += led_lightbar.o -$(MODNAME)-$(CONFIG_HWMON) += hwmon.o hwmon_fan.o hwmon_pwm.o fan.o - -KVER = $(shell uname -r) -KDIR = /lib/modules/$(KVER)/build -MDIR = /usr/src/$(MODNAME)-$(MODVER) - -all: - make -C $(KDIR) M=$(PWD) modules - -clean: - make -C $(KDIR) M=$(PWD) clean - -dkmsinstall: - mkdir -p $(MDIR) - cp Makefile dkms.conf $(wildcard *.c) $(wildcard *.h) $(MDIR)/. - dkms add $(MODNAME)/$(MODVER) - dkms build $(MODNAME)/$(MODVER) - dkms install $(MODNAME)/$(MODVER) - -dkmsuninstall: - -rmmod $(MODNAME) - -dkms uninstall $(MODNAME)/$(MODVER) - -dkms remove $(MODNAME)/$(MODVER) --all - rm -rf $(MDIR) diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/misc.c slimbook-qc71-0.2/slimbook-qc71-0.0/misc.c --- slimbook-qc71-0.1/slimbook-qc71-0.0/misc.c 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/misc.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "pr.h" - -#include -#include -#include -#include - -#include "ec.h" -#include "misc.h" -#include "util.h" - -/* ========================================================================== */ - -int qc71_rfkill_get_wifi_state(void) -{ - int err = ec_read_byte(DEVICE_STATUS_ADDR); - - if (err < 0) - return err; - - return !!(err & DEVICE_STATUS_WIFI_ON); -} - -/* ========================================================================== */ - -int qc71_fn_lock_get_state(void) -{ - int status = ec_read_byte(BIOS_CTRL_1_ADDR); - - if (status < 0) - return status; - - return !!(status & BIOS_CTRL_1_FN_LOCK_STATUS); -} - -int qc71_fn_lock_set_state(bool state) -{ - int status = ec_read_byte(BIOS_CTRL_1_ADDR); - - if (status < 0) - return status; - - status = SET_BIT(status, BIOS_CTRL_1_FN_LOCK_STATUS, state); - - return ec_write_byte(BIOS_CTRL_1_ADDR, status); -} diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/misc.h slimbook-qc71-0.2/slimbook-qc71-0.0/misc.h --- slimbook-qc71-0.1/slimbook-qc71-0.0/misc.h 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/misc.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef QC71_MISC_H -#define QC71_MISC_H - -#include - -/* ========================================================================== */ - -int qc71_rfkill_get_wifi_state(void); - -int qc71_fn_lock_get_state(void); -int qc71_fn_lock_set_state(bool state); - -#endif /* QC71_MISC_H */ diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/pdev.c slimbook-qc71-0.2/slimbook-qc71-0.0/pdev.c --- slimbook-qc71-0.1/slimbook-qc71-0.0/pdev.c 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/pdev.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,372 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "pr.h" - -#include -#include -#include -#include -#include - -#include "util.h" -#include "ec.h" -#include "features.h" -#include "misc.h" -#include "pdev.h" - -/* ========================================================================== */ - -struct platform_device *qc71_platform_dev; - -/* ========================================================================== */ - -static ssize_t fan_reduced_duty_cycle_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int status = ec_read_byte(BIOS_CTRL_3_ADDR); - - if (status < 0) - return status; - - return sprintf(buf, "%d\n", !!(status & BIOS_CTRL_3_FAN_REDUCED_DUTY_CYCLE)); -} - -static ssize_t fan_reduced_duty_cycle_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - int status; - bool value; - - if (kstrtobool(buf, &value)) - return -EINVAL; - - status = ec_read_byte(BIOS_CTRL_3_ADDR); - if (status < 0) - return status; - - status = SET_BIT(status, BIOS_CTRL_3_FAN_REDUCED_DUTY_CYCLE, value); - - status = ec_write_byte(BIOS_CTRL_3_ADDR, status); - - if (status < 0) - return status; - - return count; -} - -static ssize_t fan_always_on_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int status = ec_read_byte(BIOS_CTRL_3_ADDR); - - if (status < 0) - return status; - - return sprintf(buf, "%d\n", !!(status & BIOS_CTRL_3_FAN_ALWAYS_ON)); -} - -static ssize_t fan_always_on_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - int status; - bool value; - - if (kstrtobool(buf, &value)) - return -EINVAL; - - status = ec_read_byte(BIOS_CTRL_3_ADDR); - if (status < 0) - return status; - - status = SET_BIT(status, BIOS_CTRL_3_FAN_ALWAYS_ON, value); - - status = ec_write_byte(BIOS_CTRL_3_ADDR, status); - - if (status < 0) - return status; - - return count; -} - -static ssize_t fn_lock_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int status = qc71_fn_lock_get_state(); - - if (status < 0) - return status; - - return sprintf(buf, "%d\n", status); -} - -static ssize_t fn_lock_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - int status; - bool value; - - if (kstrtobool(buf, &value)) - return -EINVAL; - - status = qc71_fn_lock_set_state(value); - if (status < 0) - return status; - - return count; -} - -static ssize_t fn_lock_switch_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int status = ec_read_byte(AP_BIOS_BYTE_ADDR); - - if (status < 0) - return status; - - return sprintf(buf, "%d\n", !!(status & AP_BIOS_BYTE_FN_LOCK_SWITCH)); -} - -static ssize_t fn_lock_switch_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - int status; - bool value; - - if (kstrtobool(buf, &value)) - return -EINVAL; - - status = ec_read_byte(AP_BIOS_BYTE_ADDR); - if (status < 0) - return status; - - status = SET_BIT(status, AP_BIOS_BYTE_FN_LOCK_SWITCH, value); - - status = ec_write_byte(AP_BIOS_BYTE_ADDR, status); - - if (status < 0) - return status; - - return count; -} - -static ssize_t manual_control_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int status = ec_read_byte(CTRL_1_ADDR); - - if (status < 0) - return status; - - return sprintf(buf, "%d\n", !!(status & CTRL_1_MANUAL_MODE)); -} - -static ssize_t manual_control_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - int status; - bool value; - - if (kstrtobool(buf, &value)) - return -EINVAL; - - status = ec_read_byte(CTRL_1_ADDR); - if (status < 0) - return status; - - status = SET_BIT(status, CTRL_1_MANUAL_MODE, value); - - status = ec_write_byte(CTRL_1_ADDR, status); - - if (status < 0) - return status; - - return count; -} - -static ssize_t super_key_lock_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int status = ec_read_byte(STATUS_1_ADDR); - - if (status < 0) - return status; - - return sprintf(buf, "%d\n", !!(status & STATUS_1_SUPER_KEY_LOCK)); -} - -static ssize_t super_key_lock_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - int status; - bool value; - - if (kstrtobool(buf, &value)) - return -EINVAL; - - status = ec_read_byte(STATUS_1_ADDR); - if (status < 0) - return status; - - if (value != !!(status & STATUS_1_SUPER_KEY_LOCK)) { - status = ec_write_byte(TRIGGER_1_ADDR, TRIGGER_1_SUPER_KEY_LOCK); - - if (status < 0) - return status; - } - - return count; -} - -static ssize_t fan_boost_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int status = ec_read_byte(FAN_CTRL_ADDR); - - if (status < 0) - return status; - - return sprintf(buf, "%d\n", !!(status & FAN_CTRL_FAN_BOOST)); -} - -static ssize_t fan_boost_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - int status; - bool value; - - if (kstrtobool(buf, &value)) - return -EINVAL; - - status = ec_read_byte(FAN_CTRL_ADDR); - if (status < 0) - return status; - - if (value != !!(status & FAN_CTRL_FAN_BOOST)) { - status = ec_write_byte(TRIGGER_1_ADDR, TRIGGER_1_FAN_BOOST); - - if (status < 0) - return status; - } - - return count; -} - -static ssize_t silent_mode_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int status = ec_read_byte(FAN_CTRL_ADDR); - - if (status < 0) - return status; - - return sprintf(buf, "%d\n", !!(status & FAN_CTRL_SILENT_MODE)); -} - -static ssize_t silent_mode_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - int status; - bool value; - - if (kstrtobool(buf, &value)) - return -EINVAL; - - status = ec_read_byte(FAN_CTRL_ADDR); - if (status < 0) - return status; - - status = SET_BIT(status, FAN_CTRL_SILENT_MODE, value); - - status = ec_write_byte(FAN_CTRL_ADDR, status); - - if (status < 0) - return status; - - return count; -} - -/* ========================================================================== */ - -static DEVICE_ATTR_RW(fn_lock); -static DEVICE_ATTR_RW(fn_lock_switch); -static DEVICE_ATTR_RW(fan_always_on); -static DEVICE_ATTR_RW(fan_reduced_duty_cycle); -static DEVICE_ATTR_RW(manual_control); -static DEVICE_ATTR_RW(super_key_lock); -static DEVICE_ATTR_RW(fan_boost); -static DEVICE_ATTR_RW(silent_mode); - -static struct attribute *qc71_laptop_attrs[] = { - &dev_attr_fn_lock.attr, - &dev_attr_fn_lock_switch.attr, - &dev_attr_fan_always_on.attr, - &dev_attr_fan_reduced_duty_cycle.attr, - &dev_attr_manual_control.attr, - &dev_attr_super_key_lock.attr, - &dev_attr_fan_boost.attr, - &dev_attr_silent_mode.attr, - NULL -}; - -/* ========================================================================== */ - -static umode_t qc71_laptop_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) -{ - bool ok = false; - - if (attr == &dev_attr_fn_lock.attr || attr == &dev_attr_fn_lock_switch.attr) - ok = qc71_features.fn_lock; - else if (attr == &dev_attr_fan_always_on.attr || attr == &dev_attr_fan_reduced_duty_cycle.attr) - ok = qc71_features.fan_extras; - else if (attr == &dev_attr_manual_control.attr) - ok = true; - else if (attr == &dev_attr_super_key_lock.attr) - ok = qc71_features.super_key_lock; - else if (attr == &dev_attr_fan_boost.attr) - ok = qc71_features.fan_boost; - else if (attr == &dev_attr_silent_mode.attr) - ok = qc71_features.silent_mode; - - return ok ? attr->mode : 0; -} - -/* ========================================================================== */ - -static const struct attribute_group qc71_laptop_group = { - .is_visible = qc71_laptop_attr_is_visible, - .attrs = qc71_laptop_attrs, -}; - -static const struct attribute_group *qc71_laptop_groups[] = { - &qc71_laptop_group, - NULL -}; - -/* ========================================================================== */ - -int __init qc71_pdev_setup(void) -{ - int err; - - qc71_platform_dev = platform_device_alloc(KBUILD_MODNAME, PLATFORM_DEVID_NONE); - if (!qc71_platform_dev) { - err = -ENOMEM; - goto out; - } - - qc71_platform_dev->dev.groups = qc71_laptop_groups; - - err = platform_device_add(qc71_platform_dev); - if (err) { - platform_device_put(qc71_platform_dev); - qc71_platform_dev = NULL; - } - -out: - return err; -} - -void qc71_pdev_cleanup(void) -{ - /* checks for IS_ERR_OR_NULL() */ - platform_device_unregister(qc71_platform_dev); -} diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/pdev.h slimbook-qc71-0.2/slimbook-qc71-0.0/pdev.h --- slimbook-qc71-0.1/slimbook-qc71-0.0/pdev.h 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/pdev.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef QC71_PDEV_H -#define QC71_PDEV_H - -#include -#include - -/* ========================================================================== */ - -extern struct platform_device *qc71_platform_dev; - -/* ========================================================================== */ - -int __init qc71_pdev_setup(void); -void qc71_pdev_cleanup(void); - -#endif /* QC71_PDEV_H */ diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/pr.h slimbook-qc71-0.2/slimbook-qc71-0.0/pr.h --- slimbook-qc71-0.1/slimbook-qc71-0.0/pr.h 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/pr.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef QC71_PR_H -#define QC71_PR_H - -#define pr_fmt(fmt) KBUILD_MODNAME "/" KBUILD_BASENAME ": %s: " fmt, __func__ - -#include - -#endif /* QC71_PR_H */ diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/README.md slimbook-qc71-0.2/slimbook-qc71-0.0/README.md --- slimbook-qc71-0.1/slimbook-qc71-0.0/README.md 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/README.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,230 +0,0 @@ -# What is it? -This a Linux kernel platform driver for Intel Whitebook LAPQC71X systems (XMG Fusion 15, Eluktronics MAG 15, Aftershock Vapor 15, ...). - - -# Disclaimer -**This software is in early stages of developement. Futhermore, to quote GPL: everything is provided as is. There is no warranty for the program, to the extent permitted by applicable law.** - -**This software is licensed under the GNU General Public License v2.0** - - -# Compatibility -It has only been tested on an XMG Fusion 15 device (BIOS 0062 up to 0120) and with the `5.4`, and `5.8`-`5.13` kernel series. -Some functions have been confirmed to work on the Tongfang GK7C chassis (XMG Neo 17, PCS Recoil III, Walmart OP17) (see [#6][issue6]). - - -# Dependencies -### Required -* Your kernel has been compiled with `CONFIG_ACPI` and `CONFIG_DMI` (it probably was) -* Linux headers for you current kernel - -### Optional -* DKMS if you don't want to recompile it manually every time the kernel is upgraded - - -# Features -## Current -* Integrate fan speeds into the Linux hardware monitoring subsystem (so that `lm_sensors` can pick it up) -* Control the lightbar -* Enable/disable always-on mode, reduced fan duty cycle (BIOS 0114 and above) -* Fn lock (BIOS 0114 and above) -* Change battery charge limit (BIOS 0114 and above) - - -# How to install -## Downloading -If you have `git` installed: -``` -git clone https://github.com/pobrn/qc71_laptop -``` - -If you don't, then you can download it [here](https://github.com/pobrn/qc71_laptop/archive/master.zip). - -## Installing -### Linux headers -On Debian and its [many][debian-derivatives] [derivatives][ubuntu-derivatives] (Ubuntu, Pop OS, Linux Mint, ...) , run -``` -sudo apt install linux-headers-$(uname -r) -``` -to install the necessary header files. - -On Arch Linux and its derivatives (Manjaro, ...), run -``` -sudo pacman -Syu linux-headers -``` - -### DKMS (optional) -DKMS should be in your distributions repositories. `sudo apt install dkms`, `sudo pacman -Syu dkms` should work depending on your distribution. - -### The module -#### Manually -Navigate in a terminal into the directory, then execute `make`. This should compile the module. If everything went correctly, a file named `qc71_laptop.ko` should appear in the directory. - -To test the module try `sudo insmod qc71_laptop.ko`. Now you should see the fan speeds appear in the output of `sensors`, and the directory `/sys/devices/platform/qc71_laptop` should now exist. If you are done testing, unload the module using `sudo rmmod qc71_laptop`. - -Now you could create a script that inserts this module at boot from this directory, or you could install it using DKMS. - -#### With DKMS -Run -``` -sudo make dkmsinstall -``` -to install the module with DKMS. Or run -``` -sudo make dkmsuninstall -``` -to uninstall the module. - -The module should automatically load at boot after this. If you want to load it immediately, run `sudo modprobe qc71_laptop`. If it reports an error, and you're convinced your device should be supported, please open an [issue][issues]. - -## Upgrade - -If you installed the module with DKMS, and you wish to upgrade, first open the directory of the old sources, and run -``` -sudo make dkmsuninstall -``` -then update the sources (pull the repository, download the sources again manually, etc.), then run -``` -sudo make dkmsinstall -``` - - -# How to use -## Fan speeds -After loading the module the fan speeds and temperatures should immediately appear in the output of `sensors`, and all your favourite monitoring utilities (e.g. the [Freon][gnome-ext-freon] GNOME shell extension) that use `sensors`. - -## Controlling the lightbar -The lightbar is integrated into the LED subsystem of the linux kernel. When the module is loaded, `/sys/class/leds/qc71_laptop::lightbar` directory should exist with the following important files: -``` -/sys/class/leds/qc71_laptop::lightbar/brightness -``` - -It contains `1` if the lightbar is turned on in S0 sleep state (aka. when the device is powered on), `0` otherwise. You can turn on/off the lightbar by writing an appropriate number into this file: -``` -# echo 1 > /sys/class/leds/qc71_laptop::lightbar/brightness -``` -will turn the lightbar on. (Writing `0` will turn it off.) -To check the current state: -``` -$ cat /sys/class/leds/qc71_laptop::lightbar/brightness -``` - -___ -``` -/sys/class/leds/qc71_laptop::lightbar/brightness_s3 -``` -It contains `1` if the lightbar is turned on in S3 sleep state (aka. when the device is sleeping). If it is `1`, the lightbar will "breathe" when the device is sleeping. You can control it the same way as `brightness`. - -___ -``` -/sys/class/leds/qc71_laptop::lightbar/rainbow_mode -``` -It contains `1` if the "rainbow mode" is enabled (aka. the color will continuously cycle). Controlling works the same way as before. - -*Note:* Enabling/disabling the rainbow mode will not turn the lightbar on/off. -*Note:* The rainbow mode takes precedence over the color. - -___ -``` -/sys/class/leds/qc71_laptop::lightbar/color -``` -This file controls the color of the lightbar. It is a three digit number (possibly with padding zeroes). The first digit is the red component, the second one is the green componenet, the third one is the blue component. -``` -$ cat /sys/class/leds/qc71_laptop::lightbar/color -``` -tells you the current color, while -``` -# echo 591 > /sys/class/leds/qc71_laptop::lightbar/color -``` -will change the current color. - -*Note:* Chaning the color will not turn the lightbar on. - - -## Controlling the fans -These can be controlled directly from the BIOS as well. - -### Passive cooling -I call this feature "always on" because that's less confusing than "passive cooling". -``` -# echo 1 > /sys/devices/platform/qc71_laptop/fan_always_on -``` -will cause the fans to run continuously. Writing `0` will turn it off. - -### Reduced duty cycle -``` -# echo 1 > /sys/devices/platform/qc71_laptop/fan_reduced_duty_cycle -``` -will cause the fans to run at 25% of their capacity (about 2300 RPM) at idle (instead of 30% - about 2700 RPM). Writing `0` will restore the 30% idle duty cycle. - -## Fn lock -``` -# echo 1 > /sys/devices/platform/qc71_laptop/fn_lock_switch -``` -will enable changing the Fn lock state by pressing Fn+ESC. If this file contains `1`, then pressing Fn+ESC will toggle the Fn lock; if this file contains `0`, then pressing Fn+ESC will have no effect on the Fn lock. - -``` -# echo 1 > /sys/devices/platform/qc71_laptop/fn_lock -``` -will directly enable the Fn lock. If this file contains `1`, then pressing the functions keys will trigger their secondary functions (mute, brightness up, etc.); if this file contains `0`, then pressing the functions keys will trigger their primary functions (F1, F2, ...). - -## Battery charge limit -The file `/sys/class/power_supply/BAT0/charge_control_end_threshold` contains the current charge threshold. Writing a number between 1 and 100 will cause the battery charging limit to be set to that percentage. (I did not test extremely low values, so I cannot say if they work). For example: -``` -# echo 60 > /sys/class/power_supply/BAT0/charge_control_end_threshold -``` -will cause charging to stop when the battery reaches 60% of its capacity. - -## Super key (windows key) lock -It is possible to disable the super (windows) key by pressing Fn+F2 (or just F2 if the Fn lock is enabled). This can be also achieved by changing the writing the appropriate value into `/sys/devices/platform/qc71_laptop/super_key_lock`. -``` -# echo 1 > /sys/devices/platform/qc71_laptop/super_key_lock -``` -disables the super key, while -``` -# echo 0 > /sys/devices/platform/qc71_laptop/super_key_lock -``` -enables it. Reading the file will provide information about the current state of the super key. `0` means enabled, `1` means disabled. - -## Example use - -The XMG Control Center can change the color if the device is on battery or plugged in. Fortunately you can easily achieve the same using [acpid](https://wiki.archlinux.org/index.php/Acpid). Modifying the appropriate part of `/etc/acpi/handler.sh` like this: -``` - ac_adapter) - case "$2" in - AC|ACAD|ADP0|ACPI0003:00) # the "ACPI0003:00" part was not there by default - case "$4" in - 00000000) - logger 'AC unplugged' - - # change color to red - echo 900 > /sys/class/leds/qc71_laptop::lightbar/color - ;; - 00000001) - logger 'AC plugged' - - # change color to blue - echo 009 > /sys/class/leds/qc71_laptop::lightbar/color - ;; - esac - ;; - *) - logger "ACPI action undefined: $2" - ;; - esac - ;; -``` -You can use `acpi_listen` to see what events are generated when you plug the machine in or disconnect the charger. You might need to modify the third line (in this snippet). - - -# Troubleshooting - -* The [TUXEDO Control Center][tcc-github] may interfere with the operation of this kernel module. I do not recommend using both at the same time. - - -[issue6]: https://github.com/pobrn/qc71_laptop/issues/6 -[debian-derivatives]: https://www.debian.org/derivatives/ -[ubuntu-derivatives]: https://wiki.ubuntu.com/DerivativeTeam/Derivatives -[issues]: https://github.com/pobrn/qc71_laptop/issues -[gnome-ext-freon]: https://extensions.gnome.org/extension/841/freon/ -[tcc-github]: https://github.com/tuxedocomputers/tuxedo-control-center diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/util.h slimbook-qc71-0.2/slimbook-qc71-0.0/util.h --- slimbook-qc71-0.1/slimbook-qc71-0.0/util.h 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/util.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef QC71_UTIL_H -#define QC71_UTIL_H - -#define SET_BIT(value, bit, on) ((on) ? ((value) | (bit)) : ((value) & ~(bit))) - -#endif /* QC71_UTIL_H */ diff -Nru slimbook-qc71-0.1/slimbook-qc71-0.0/wmi.h slimbook-qc71-0.2/slimbook-qc71-0.0/wmi.h --- slimbook-qc71-0.1/slimbook-qc71-0.0/wmi.h 2022-08-17 10:06:06.000000000 +0000 +++ slimbook-qc71-0.2/slimbook-qc71-0.0/wmi.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifndef QC71_LAPTOP_WMI_H -#define QC71_LAPTOP_WMI_H - -/* ========================================================================== */ -/* WMI methods */ - -/* AcpiTest_MULong */ -#define QC71_WMI_WMBC_GUID "ABBC0F6F-8EA1-11D1-00A0-C90629100000" -#define QC71_WMBC_GETSETULONG_ID 4 - -/* ========================================================================== */ -/* WMI events */ - -/* AcpiTest_EventULong */ -#define QC71_WMI_EVENT0_GUID "ABBC0F72-8EA1-11D1-00A0-C90629100000" - -/* AcpiTest_EventString */ -#define QC71_WMI_EVENT1_GUID "ABBC0F71-8EA1-11D1-00A0-C90629100000" - -/* AcpiTest_EventPackage */ -#define QC71_WMI_EVENT2_GUID "ABBC0F70-8EA1-11D1-00A0-C90629100000" - -#endif /* QC71_LAPTOP_WMI_H */ diff -Nru slimbook-qc71-0.1/.vscode/configurationCache.log slimbook-qc71-0.2/.vscode/configurationCache.log --- slimbook-qc71-0.1/.vscode/configurationCache.log 2022-08-17 13:02:00.000000000 +0000 +++ slimbook-qc71-0.2/.vscode/configurationCache.log 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -{"buildTargets":["all","clean","install"],"launchTargets":[],"customConfigurationProvider":{"workspaceBrowse":{"browsePath":[],"compilerArgs":[]},"fileIndex":[]}} \ No newline at end of file diff -Nru slimbook-qc71-0.1/.vscode/dryrun.log slimbook-qc71-0.2/.vscode/dryrun.log --- slimbook-qc71-0.1/.vscode/dryrun.log 2022-08-17 13:02:00.000000000 +0000 +++ slimbook-qc71-0.2/.vscode/dryrun.log 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -make --dry-run --always-make --keep-going --print-directory -make: Entering directory '/home/programacion/Escritorio/Slimbook_Dev_Apps/slimbook-qc71-dkms-0.0/slimbook-qc71-0.0' -make: Nothing to be done for 'all'. -make: Leaving directory '/home/programacion/Escritorio/Slimbook_Dev_Apps/slimbook-qc71-dkms-0.0/slimbook-qc71-0.0' - diff -Nru slimbook-qc71-0.1/.vscode/settings.json slimbook-qc71-0.2/.vscode/settings.json --- slimbook-qc71-0.1/.vscode/settings.json 2022-08-17 11:18:06.000000000 +0000 +++ slimbook-qc71-0.2/.vscode/settings.json 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -{ - "makefile.extensionOutputFolder": "./.vscode" -} \ No newline at end of file diff -Nru slimbook-qc71-0.1/.vscode/targets.log slimbook-qc71-0.2/.vscode/targets.log --- slimbook-qc71-0.1/.vscode/targets.log 2022-08-17 13:02:00.000000000 +0000 +++ slimbook-qc71-0.2/.vscode/targets.log 1970-01-01 00:00:00.000000000 +0000 @@ -1,339 +0,0 @@ -make all --print-data-base --no-builtin-variables --no-builtin-rules --question -# GNU Make 4.2.1 -# Built for x86_64-pc-linux-gnu -# Copyright (C) 1988-2016 Free Software Foundation, Inc. -# License GPLv3+: GNU GPL version 3 or later -# This is free software: you are free to change and redistribute it. -# There is NO WARRANTY, to the extent permitted by law. - -# Make data base, printed on Wed Aug 17 15:02:00 2022 - -# Variables - -# 'override' directive -GNUMAKEFLAGS := -# automatic -