| #!/bin/bash |
| # Copyright 2021 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| # This is intended to be used as a library for managing gpio line values. |
| # Executing this directly will do nothing. |
| [ -n "${gpio_ctrl_init-}" ] && return |
| |
| # Map names of GPIOs to GPIO number |
| # This maps a schematic name to a gpiochip and line at a specific offset |
| declare -A GPIO_NAMES_TO_SCM=( |
| # Examples |
| #['SYS_RESET_N']='label=/pinctrl@f0800000/gpio@f0012000 21' |
| #['PWRBTN_N']='label=/pinctrl@f0800000/gpio@f0012000 23' |
| #['PCH_SLP_S5_R_N']='of_node=/ahb/apb/i2c@8e000/cpu_seq@6b 9' |
| #['PCH_PWRGOOD_R']='of_node=/ahb/apb/i2c@8e000/cpu_seq@6b 6' |
| ) |
| |
| # Load configurations from a known location in the filesystem to populate |
| # named GPIOs |
| shopt -s nullglob |
| for conf in /usr/share/gpio-ctrl/conf.d/*.sh; do |
| # shellcheck source=/dev/null |
| source "$conf" |
| done |
| |
| declare -A gpio_sysfs_lookup_cache=() |
| declare -A gpio_lookup_cache=() |
| |
| declare -A gpio_init=() |
| |
| ################################################## |
| # Looks up the sysfs GPIO number |
| # Arguments: |
| # $1: GPIO name |
| # Return: |
| # 0 if success, non-zero if error |
| # stdout: The GPIO number |
| ################################################## |
| gpio_name_to_num() { |
| local name="$1" |
| |
| if [ -n "${gpio_lookup_cache["$name"]+1}" ]; then |
| echo "${gpio_lookup_cache["$name"]}" |
| return 0 |
| fi |
| |
| local scm="${GPIO_NAMES_TO_SCM["$name"]-}" |
| if [ -z "$scm" ]; then |
| echo "Missing gpio definition: $name" >&2 |
| return 1 |
| fi |
| local id="${scm% *}" |
| local type="${id%=*}" |
| local val="${id#*=}" |
| local offset="${scm#* }" |
| |
| local sysfs |
| if [ -n "${gpio_sysfs_lookup_cache["$id"]+1}" ]; then |
| sysfs="${gpio_sysfs_lookup_cache["$id"]}" |
| else |
| case "$type" in |
| label) |
| if ! sysfs="$(grep -xl "$val" /sys/class/gpio/gpiochip*/label)"; then |
| echo "Failed to find gpiochip: $val" >&2 |
| return 1 |
| fi |
| sysfs="${sysfs%/label}" |
| ;; |
| of_node) |
| for sysfs in $(echo /sys/class/gpio/gpiochip*); do |
| local link |
| # Ignore errors because not all devices have of_nodes |
| link="$(readlink -f "$sysfs/device/of_node" 2>/dev/null)" || continue |
| [ "${link#/sys/firmware/devicetree/base}" = "$val" ] && break |
| sysfs= |
| done |
| if [ -z "$sysfs" ]; then |
| echo "Failed to find gpiochip: $val" >&2 |
| return 1 |
| fi |
| ;; |
| *) |
| echo "Invalid GPIO type $type" >&2 |
| return 1 |
| ;; |
| esac |
| gpio_sysfs_lookup_cache["$id"]="$sysfs" |
| fi |
| |
| local ngpio |
| ngpio=$(<"$sysfs"/ngpio) |
| if (( ngpio <= offset )); then |
| echo "$name with gpiochip $sysfs only has $ngpio but wants $offset" >&2 |
| return 1 |
| fi |
| |
| gpio_lookup_cache["$name"]=$(( $(<"$sysfs"/base) + offset )) |
| echo "${gpio_lookup_cache["$name"]}" |
| } |
| |
| |
| ################################################## |
| # Populates the GPIO lookup cache |
| # Most calls to gpio_name_to_num that would |
| # normally cache the sysfs lookups for gpios run |
| # inside subshells. This prevents them from |
| # populating a global cache and greatly speeding |
| # up future lookups. This call allows scripts to |
| # populate the cache prior to looking up gpios. |
| ################################################## |
| gpio_build_cache() { |
| local timeout="${1-0}" |
| shift |
| local gpios=("$@") |
| |
| if (( ${#gpios[@]} == 0 )); then |
| gpios=("${!GPIO_NAMES_TO_SCM[@]}") |
| fi |
| |
| local deadline=$(( timeout + SECONDS )) |
| local name |
| for name in "${gpios[@]}"; do |
| while true; do |
| gpio_name_to_num "$name" >/dev/null && break |
| if (( deadline <= SECONDS )); then |
| echo "Timed out building gpio cache" >&2 |
| return 1 |
| fi |
| done |
| done |
| } |
| |
| ################################################## |
| # Initializes the gpio state |
| # This operation is idempotent and can be applied |
| # repeatedly to the same gpio. It will make sure the |
| # gpio ends up in the initialized state even if it |
| # was. |
| # Arguments: |
| # $1: GPIO name |
| # Return: |
| # 0 if success, non-zero if error |
| ################################################## |
| gpio_init_() { |
| local name="$1" |
| local num="$2" |
| |
| if [ -n "${gpio_init["$name"]+1}" ]; then |
| return 0 |
| fi |
| |
| if [ ! -e "/sys/class/gpio/gpio$num" ]; then |
| if ! echo "$num" >'/sys/class/gpio/export'; then |
| echo "Failed to export $name gpio$num" >&2 |
| return 1 |
| fi |
| fi |
| local active_low=0 |
| if [[ "${name%_N}" != "$name" ]]; then |
| active_low=1 |
| fi |
| if ! echo "$active_low" >"/sys/class/gpio/gpio$num/active_low"; then |
| echo "Failed to set active_low for $name gpio$num" >&2 |
| return 1 |
| fi |
| gpio_init["$name"]=1 |
| } |
| gpio_init() { |
| local name="$1" |
| |
| # Ensure the cache is updated by not running in a subshell |
| gpio_name_to_num "$name" >/dev/null || return |
| |
| gpio_init_ "$name" "$(gpio_name_to_num "$name")" |
| } |
| |
| ################################################## |
| # Sets the output GPIO value. |
| # Arguments: |
| # $1: GPIO name |
| # $2: GPIO value, "1" or "0" |
| # Return: |
| # 0 if success, non-zero if error |
| ################################################## |
| gpio_set_value_() { |
| local name="$1" |
| local num="$2" |
| local val="$3" |
| |
| gpio_init_ "$name" "$num" || return |
| if ! echo out >"/sys/class/gpio/gpio$num/direction"; then |
| echo "Failed to set output for $name gpio$num" >&2 |
| return 1 |
| fi |
| if ! echo "$val" >"/sys/class/gpio/gpio$num/value"; then |
| echo "Failed to set $name gpio$num = $val" >&2 |
| return 1 |
| fi |
| } |
| gpio_set_value() { |
| local name="$1" |
| local val="$2" |
| |
| # Ensure the cache is updated by not running in a subshell |
| gpio_name_to_num "$name" >/dev/null || return |
| |
| gpio_set_value_ "$name" "$(gpio_name_to_num "$name")" "$val" |
| } |
| |
| ################################################## |
| # Get GPIO value |
| # Arguments: |
| # $1: GPIO name |
| # Return: |
| # 0 if success, non-zero if error |
| # stdout: The value of the gpio |
| ################################################## |
| gpio_get_value_() { |
| local name="$1" |
| local num="$2" |
| |
| gpio_init_ "$name" "$num" || return |
| if ! cat "/sys/class/gpio/gpio$num/value"; then |
| echo "Failed to get $name gpio$num value" >&2 |
| return 1 |
| fi |
| } |
| gpio_get_value() { |
| local name="$1" |
| |
| # Ensure the cache is updated by not running in a subshell |
| gpio_name_to_num "$name" >/dev/null || return |
| |
| gpio_get_value_ "$name" "$(gpio_name_to_num "$name")" |
| } |
| |
| gpio_ctrl_init=1 |