요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
Supporting Legacy Boards
========================
Many drivers in the kernel, such as ``leds-gpio`` and ``gpio-keys``, are
migrating away from using board-specific ``platform_data`` to a unified device
properties interface. This interface allows drivers to be simpler and more
generic, as they can query properties in a standardized way.
On modern systems, these properties are provided via device tree. However, some
older platforms have not been converted to device tree and instead rely on
board files to describe their hardware configuration. To bridge this gap and
allow these legacy boards to work with modern, generic drivers, the kernel
provides a mechanism called **software nodes**.
This document provides a guide on how to convert a legacy board file from using
``platform_data`` and ``gpiod_lookup_table`` to the modern software node
approach for describing GPIO-connected devices.
The Core Idea: Software Nodes
-----------------------------
Software nodes allow board-specific code to construct an in-memory,
device-tree-like structure using struct software_node and struct
property_entry. This structure can then be associated with a platform device,
allowing drivers to use the standard device properties API (e.g.,
device_property_read_u32(), device_property_read_string()) to query
configuration, just as they would on an ACPI or device tree system.
The gpiolib code has support for handling software nodes, so that if GPIO is
described properly, as detailed in the section below, then regular gpiolib APIs,
such as gpiod_get(), gpiod_get_optional(), and others will work.
Requirements for GPIO Properties
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When using software nodes to describe GPIO connections, the following
requirements must be met for the GPIO core to correctly resolve the reference:
1. **The GPIO controller's software node "name" must match the controller's
"label".** The gpiolib core uses this name to find the corresponding
struct gpio_chip at runtime.
This software node has to be registered, but need not be attached to the
device representing the GPIO controller that is providing the GPIO in
question. It may be left as a "free floating" node.
2. **The GPIO property must be a reference.** The ``PROPERTY_ENTRY_GPIO()``
macro handles this as it is an alias for ``PROPERTY_ENTRY_REF()``.
3. **The reference must have exactly two arguments:**
- The first argument is the GPIO offset within the controller.
- The second argument is the flags for the GPIO line (e.g.,
GPIO_ACTIVE_HIGH, GPIO_ACTIVE_LOW).
The ``PROPERTY_ENTRY_GPIO()`` macro is the preferred way of defining GPIO
properties in software nodes.
Conversion Example
------------------
Let's walk through an example of converting a board file that defines a GPIO-
connected LED and a button.
Before: Using Platform Data
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A typical legacy board file might look like this:
.. code-block:: c
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/gpio_keys.h>
#include <linux/gpio/machine.h>
#define MYBOARD_GPIO_CONTROLLER "gpio-foo"
/* LED setup */
static const struct gpio_led myboard_leds[] = {
{
.name = "myboard:green:status",
.default_trigger = "heartbeat",
},
};
static const struct gpio_led_platform_data myboard_leds_pdata = {
.num_leds = ARRAY_SIZE(myboard_leds),
.leds = myboard_leds,
};
static struct gpiod_lookup_table myboard_leds_gpios = {
.dev_id = "leds-gpio",
.table = {
GPIO_LOOKUP_IDX(MYBOARD_GPIO_CONTROLLER, 42, NULL, 0, GPIO_ACTIVE_HIGH),
{ },
},
};
/* Button setup */
static struct gpio_keys_button myboard_buttons[] = {
{
.code = KEY_WPS_BUTTON,
.desc = "WPS Button",
.active_low = 1,
},
};
static const struct gpio_keys_platform_data myboard_buttons_pdata = {
.buttons = myboard_buttons,
.nbuttons = ARRAY_SIZE(myboard_buttons),
};
static struct gpiod_lookup_table myboard_buttons_gpios = {
.dev_id = "gpio-keys",
.table = {
GPIO_LOOKUP_IDX(MYBOARD_GPIO_CONTROLLER, 15, NULL, 0, GPIO_ACTIVE_LOW),
{ },
},
};
/* Device registration */
static int __init myboard_init(void)
{
gpiod_add_lookup_table(&myboard_leds_gpios);
gpiod_add_lookup_table(&myboard_buttons_gpios);
platform_device_register_data(NULL, "leds-gpio", -1,
&myboard_leds_pdata, sizeof(myboard_leds_pdata));
platform_device_register_data(NULL, "gpio-keys", -1,
&myboard_buttons_pdata, sizeof(myboard_buttons_pdata));
return 0;
}
After: Using Software Nodes
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here is how the same configuration can be expressed using software nodes.
Step 1: Define the GPIO Controller Node
***************************************
First, define a software node that represents the GPIO controller that the
LEDs and buttons are connected to. The ``name`` of this node must match the
name of the driver for the GPIO controller (e.g., "gpio-foo").
.. code-block:: c
#include <linux/property.h>
#include <linux/gpio/property.h>
#define MYBOARD_GPIO_CONTROLLER "gpio-foo"
static const struct software_node myboard_gpio_controller_node = {
.name = MYBOARD_GPIO_CONTROLLER,
};
Step 2: Define Consumer Device Nodes and Properties
***************************************************
Next, define the software nodes for the consumer devices (the LEDs and buttons).
This involves creating a parent node for each device type and child nodes for
each individual LED or button.
.. code-block:: c
/* LED setup */
static const struct software_node myboard_leds_node = {
.name = "myboard-leds",
};
static const struct property_entry myboard_status_led_props[] = {
PROPERTY_ENTRY_STRING("label", "myboard:green:status"),
PROPERTY_ENTRY_STRING("linux,default-trigger", "heartbeat"),
PROPERTY_ENTRY_GPIO("gpios", &myboard_gpio_controller_node, 42, GPIO_ACTIVE_HIGH),
{ }
};
static const struct software_node myboard_status_led_swnode = {
.name = "status-led",
.parent = &myboard_leds_node,
.properties = myboard_status_led_props,
};
/* Button setup */
static const struct software_node myboard_keys_node = {
.name = "myboard-keys",
};
static const struct property_entry myboard_wps_button_props[] = {
PROPERTY_ENTRY_STRING("label", "WPS Button"),
PROPERTY_ENTRY_U32("linux,code", KEY_WPS_BUTTON),
PROPERTY_ENTRY_GPIO("gpios", &myboard_gpio_controller_node, 15, GPIO_ACTIVE_LOW),
{ }
};
static const struct software_node myboard_wps_button_swnode = {
.name = "wps-button",
.parent = &myboard_keys_node,
.properties = myboard_wps_button_props,
};
Step 3: Group and Register the Nodes
************************************
For maintainability, it is often beneficial to group all software nodes into a
single array and register them with one call.
.. code-block:: c
static const struct software_node * const myboard_swnodes[] = {
&myboard_gpio_controller_node,
&myboard_leds_node,
&myboard_status_led_swnode,
&myboard_keys_node,
&myboard_wps_button_swnode,
NULL
};
static int __init myboard_init(void)
{
int error;
error = software_node_register_node_group(myboard_swnodes);
if (error) {
pr_err("Failed to register software nodes: %d\n", error);
return error;
}
// ... platform device registration follows
}
.. note::
When splitting registration of nodes by devices that they represent, it is
essential that the software node representing the GPIO controller itself
is registered first, before any of the nodes that reference it.
Step 4: Register Platform Devices with Software Nodes
*****************************************************
Finally, register the platform devices and associate them with their respective
software nodes using the ``fwnode`` field in struct platform_device_info.
.. code-block:: c
static struct platform_device *leds_pdev;
static struct platform_device *keys_pdev;
static int __init myboard_init(void)
{
struct platform_device_info pdev_info;
int error;
error = software_node_register_node_group(myboard_swnodes);
if (error)
return error;
memset(&pdev_info, 0, sizeof(pdev_info));
pdev_info.name = "leds-gpio";
pdev_info.id = PLATFORM_DEVID_NONE;
pdev_info.fwnode = software_node_fwnode(&myboard_leds_node);
leds_pdev = platform_device_register_full(&pdev_info);
if (IS_ERR(leds_pdev)) {
error = PTR_ERR(leds_pdev);
goto err_unregister_nodes;
}
memset(&pdev_info, 0, sizeof(pdev_info));
pdev_info.name = "gpio-keys";
pdev_info.id = PLATFORM_DEVID_NONE;
pdev_info.fwnode = software_node_fwnode(&myboard_keys_node);
keys_pdev = platform_device_register_full(&pdev_info);
if (IS_ERR(keys_pdev)) {
error = PTR_ERR(keys_pdev);
platform_device_unregister(leds_pdev);
goto err_unregister_nodes;
}
return 0;
err_unregister_nodes:
software_node_unregister_node_group(myboard_swnodes);
return error;
}
static void __exit myboard_exit(void)
{
platform_device_unregister(keys_pdev);
platform_device_unregister(leds_pdev);
software_node_unregister_node_group(myboard_swnodes);
}
With these changes, the generic ``leds-gpio`` and ``gpio-keys`` drivers will
be able to probe successfully and get their configuration from the properties
defined in the software nodes, removing the need for board-specific platform
data.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
Legacy board를 software node로 이전
1-18문서 제목은 `Supporting Legacy Boards`입니다. `leds-gpio`, `gpio-keys` 같은 많은 kernel driver가 board별 `platform_data`에서 통합 device-properties interface로 이동하고 있습니다. Driver는 표준 방식으로 property를 조회할 수 있어 더 단순하고 generic해집니다.
Modern system은 이 property를 Device Tree로 제공합니다. 그러나 Device Tree로 전환되지 않은 오래된 platform은 board file에 hardware configuration을 기술합니다. Kernel은 이런 legacy board와 modern generic driver 사이의 간극을 메우기 위해 software node mechanism을 제공합니다.
이 문서는 GPIO-connected device를 기술하던 legacy board file의 `platform_data`와 `gpiod_lookup_table`을 modern software-node 방식으로 변환하는 절차를 설명합니다.
Board-specific data를 표준 property interface로 옮기는 방향입니다.
Software node의 핵심 개념
19-32Software node를 사용하면 board-specific code가 `struct software_node`와 `struct property_entry`로 memory 안에 Device-Tree와 비슷한 구조를 만들 수 있습니다.
이 구조를 platform device와 연결하면 driver는 ACPI나 Device Tree system에서처럼 `device_property_read_u32()`, `device_property_read_string()` 같은 standard device-properties API로 configuration을 조회합니다.
Gpiolib은 software node를 처리합니다. 다음 절의 규칙에 맞게 GPIO를 기술하면 `gpiod_get()`, `gpiod_get_optional()` 등 일반 gpiolib API가 그대로 동작합니다.
Board code의 in-memory description이 generic driver에 전달됩니다.
GPIO property 참조 요구사항
33-57Software node로 GPIO connection을 기술할 때 GPIO core가 reference를 올바르게 해석하려면 세 요구사항을 지켜야 합니다.
- GPIO controller software node의 `name`은 controller의 `label`과 같아야 합니다. Gpiolib core가 runtime에 대응 `struct gpio_chip`을 찾는 key입니다. 이 node는 등록해야 하지만 GPIO controller device에 attach할 필요는 없고 free-floating node여도 됩니다.
- GPIO property는 reference여야 합니다. `PROPERTY_ENTRY_GPIO()`는 `PROPERTY_ENTRY_REF()`의 alias이므로 이 형식을 자동으로 만듭니다.
- Reference argument는 정확히 두 개여야 합니다. 첫째는 controller 내부 GPIO offset, 둘째는 `GPIO_ACTIVE_HIGH`·`GPIO_ACTIVE_LOW` 같은 line flag입니다.
Software node에서 GPIO property를 정의할 때는 `PROPERTY_ENTRY_GPIO()` macro 사용을 권장합니다.
Controller 식별과 reference argument를 정리했습니다.
변환 전: platform data와 lookup table
58-134예제는 GPIO에 연결된 LED와 button을 정의하는 board file을 변환합니다. 전형적인 legacy board file은 다음과 같습니다.
.. code-block:: c
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/gpio_keys.h>
#include <linux/gpio/machine.h>
#define MYBOARD_GPIO_CONTROLLER "gpio-foo"
/* LED setup */
static const struct gpio_led myboard_leds[] = {
{
.name = "myboard:green:status",
.default_trigger = "heartbeat",
},
};
static const struct gpio_led_platform_data myboard_leds_pdata = {
.num_leds = ARRAY_SIZE(myboard_leds),
.leds = myboard_leds,
};
static struct gpiod_lookup_table myboard_leds_gpios = {
.dev_id = "leds-gpio",
.table = {
GPIO_LOOKUP_IDX(MYBOARD_GPIO_CONTROLLER, 42, NULL, 0, GPIO_ACTIVE_HIGH),
{ },
},
};
/* Button setup */
static struct gpio_keys_button myboard_buttons[] = {
{
.code = KEY_WPS_BUTTON,
.desc = "WPS Button",
.active_low = 1,
},
};
static const struct gpio_keys_platform_data myboard_buttons_pdata = {
.buttons = myboard_buttons,
.nbuttons = ARRAY_SIZE(myboard_buttons),
};
static struct gpiod_lookup_table myboard_buttons_gpios = {
.dev_id = "gpio-keys",
.table = {
GPIO_LOOKUP_IDX(MYBOARD_GPIO_CONTROLLER, 15, NULL, 0, GPIO_ACTIVE_LOW),
{ },
},
};
/* Device registration */
static int __init myboard_init(void)
{
gpiod_add_lookup_table(&myboard_leds_gpios);
gpiod_add_lookup_table(&myboard_buttons_gpios);
platform_device_register_data(NULL, "leds-gpio", -1,
&myboard_leds_pdata, sizeof(myboard_leds_pdata));
platform_device_register_data(NULL, "gpio-keys", -1,
&myboard_buttons_pdata, sizeof(myboard_buttons_pdata));
return 0;
}
LED 쪽은 `gpio_led_platform_data`와 `gpiod_lookup_table`을 따로 만들고 controller `gpio-foo`의 offset 42를 `GPIO_ACTIVE_HIGH`로 연결합니다. Button 쪽은 `gpio_keys_platform_data`와 별도 lookup table을 만들고 offset 15를 `GPIO_ACTIVE_LOW`로 연결합니다.
초기화에서는 `gpiod_add_lookup_table()`을 두 번 호출한 뒤 `platform_device_register_data()`로 `leds-gpio`와 `gpio-keys`에 board-specific platform data를 전달합니다.
Platform data와 lookup table에 흩어진 LED·button 정보를 구조화했습니다.
1단계: GPIO controller node 정의
135-157같은 configuration을 software node로 표현하는 첫 단계는 LED와 button이 연결된 GPIO controller를 나타내는 node를 정의하는 것입니다. Node의 `name`은 GPIO controller driver 이름, 예제에서는 `gpio-foo`와 같아야 합니다.
.. code-block:: c
#include <linux/property.h>
#include <linux/gpio/property.h>
#define MYBOARD_GPIO_CONTROLLER "gpio-foo"
static const struct software_node myboard_gpio_controller_node = {
.name = MYBOARD_GPIO_CONTROLLER,
};
`linux/property.h`와 `linux/gpio/property.h`를 include하고 `myboard_gpio_controller_node`의 name에 `MYBOARD_GPIO_CONTROLLER`를 지정합니다.
Gpiolib이 gpio_chip을 찾는 controller node 구성입니다.
2단계: Consumer node와 property 정의
158-204다음으로 LED와 button consumer device의 software node를 정의합니다. Device type별 parent node를 만들고 각 LED 또는 button마다 child node를 둡니다.
.. code-block:: c
/* LED setup */
static const struct software_node myboard_leds_node = {
.name = "myboard-leds",
};
static const struct property_entry myboard_status_led_props[] = {
PROPERTY_ENTRY_STRING("label", "myboard:green:status"),
PROPERTY_ENTRY_STRING("linux,default-trigger", "heartbeat"),
PROPERTY_ENTRY_GPIO("gpios", &myboard_gpio_controller_node, 42, GPIO_ACTIVE_HIGH),
{ }
};
static const struct software_node myboard_status_led_swnode = {
.name = "status-led",
.parent = &myboard_leds_node,
.properties = myboard_status_led_props,
};
/* Button setup */
static const struct software_node myboard_keys_node = {
.name = "myboard-keys",
};
static const struct property_entry myboard_wps_button_props[] = {
PROPERTY_ENTRY_STRING("label", "WPS Button"),
PROPERTY_ENTRY_U32("linux,code", KEY_WPS_BUTTON),
PROPERTY_ENTRY_GPIO("gpios", &myboard_gpio_controller_node, 15, GPIO_ACTIVE_LOW),
{ }
};
static const struct software_node myboard_wps_button_swnode = {
.name = "wps-button",
.parent = &myboard_keys_node,
.properties = myboard_wps_button_props,
};
LED property는 `label`, `linux,default-trigger`, `gpios`를 가지며 GPIO reference는 controller node, offset 42, `GPIO_ACTIVE_HIGH`를 지정합니다. `status-led` child node는 `myboard-leds` parent와 이 property array를 연결합니다.
Button property는 `label`, `linux,code`, `gpios`를 가지며 controller node의 offset 15와 `GPIO_ACTIVE_LOW`를 참조합니다. `wps-button` child node는 `myboard-keys` parent에 속합니다.
Parent·child node와 GPIO property를 정리했습니다.
3단계: Node group 등록
205-239유지보수를 위해 모든 software node를 하나의 array로 묶고 한 번의 호출로 등록하는 것이 좋습니다.
.. code-block:: c
static const struct software_node * const myboard_swnodes[] = {
&myboard_gpio_controller_node,
&myboard_leds_node,
&myboard_status_led_swnode,
&myboard_keys_node,
&myboard_wps_button_swnode,
NULL
};
static int __init myboard_init(void)
{
int error;
error = software_node_register_node_group(myboard_swnodes);
if (error) {
pr_err("Failed to register software nodes: %d\n", error);
return error;
}
// ... platform device registration follows
}
Array에는 controller node, LED parent와 child, key parent와 child를 순서대로 넣고 `NULL`로 끝냅니다. `software_node_register_node_group()`이 실패하면 error를 출력하고 그대로 반환합니다.
Device별로 node 등록을 나눈다면 GPIO controller를 나타내는 software node를 이를 reference하는 어느 node보다 먼저 등록해야 합니다.
Reference target인 controller를 먼저 준비하는 의존 관계입니다.
4단계: Platform device와 software node 연결
240-298마지막으로 platform device를 등록하면서 `struct platform_device_info`의 `fwnode` field로 각 software node를 연결합니다.
.. code-block:: c
static struct platform_device *leds_pdev;
static struct platform_device *keys_pdev;
static int __init myboard_init(void)
{
struct platform_device_info pdev_info;
int error;
error = software_node_register_node_group(myboard_swnodes);
if (error)
return error;
memset(&pdev_info, 0, sizeof(pdev_info));
pdev_info.name = "leds-gpio";
pdev_info.id = PLATFORM_DEVID_NONE;
pdev_info.fwnode = software_node_fwnode(&myboard_leds_node);
leds_pdev = platform_device_register_full(&pdev_info);
if (IS_ERR(leds_pdev)) {
error = PTR_ERR(leds_pdev);
goto err_unregister_nodes;
}
memset(&pdev_info, 0, sizeof(pdev_info));
pdev_info.name = "gpio-keys";
pdev_info.id = PLATFORM_DEVID_NONE;
pdev_info.fwnode = software_node_fwnode(&myboard_keys_node);
keys_pdev = platform_device_register_full(&pdev_info);
if (IS_ERR(keys_pdev)) {
error = PTR_ERR(keys_pdev);
platform_device_unregister(leds_pdev);
goto err_unregister_nodes;
}
return 0;
err_unregister_nodes:
software_node_unregister_node_group(myboard_swnodes);
return error;
}
static void __exit myboard_exit(void)
{
platform_device_unregister(keys_pdev);
platform_device_unregister(leds_pdev);
software_node_unregister_node_group(myboard_swnodes);
}
먼저 node group을 등록한 뒤 `leds-gpio`의 `pdev_info.fwnode`에 `myboard_leds_node`의 fwnode를 지정하고 `platform_device_register_full()`을 호출합니다. 실패하면 node group을 unregister합니다.
이어서 `gpio-keys` device를 `myboard_keys_node`와 연결합니다. 두 번째 등록이 실패하면 먼저 등록한 LED platform device도 unregister한 뒤 node group을 정리합니다.
정상 종료 path에서는 key device, LED device, software-node group 순으로 unregister합니다. 이 변경으로 generic `leds-gpio`와 `gpio-keys` driver가 성공적으로 probe하고 software-node property에서 configuration을 얻으므로 board-specific platform data가 필요 없어집니다.
두 consumer device의 fwnode 연결과 error cleanup 순서입니다.
변환 전후 configuration 전달 방식을 비교합니다.
요약과 해설
legacy-boards.rst:1-298Device Tree가 없는 legacy board도 software node를 통해 modern generic GPIO driver를 사용할 수 있습니다. Controller node name은 gpio_chip label과 같아야 하고 GPIO property는 provider reference, offset, polarity flag를 정확히 담아야 합니다.
Controller node를 먼저 등록하고 consumer parent·child node를 묶은 뒤 platform device의 fwnode에 연결합니다. Error path에서는 platform device와 node group을 의존성의 역순으로 정리합니다.