#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
struct led_dev
{
unsigned int led_mask;
const char *led_name;
char led_value[8];
struct miscdevice led_misc_device;
};
/* 페리페럴 컨트롤 레지스터 주소 */
#define BCM2710_PERI_BASE 0x3F000000
/* GPIO 컨트롤 레지스터 주소 */
#define GPIO_BASE (BCM2710_PERI_BASE + 0x200000)
#define GPIO_27 27
#define GPIO_22 22
#define GPIO_26 26
/* led 1로 set 하기 위함 */
#define GPIO_27_INDEX 1 << (GPIO_27 % 32)
#define GPIO_22_INDEX 1 << (GPIO_22 % 32)
#define GPIO_26_INDEX 1 << (GPIO_26 % 32)
/* output으로 설정 */
#define GPIO_27_FUNC 1 << ((GPIO_27 % 10) * 3)
#define GPIO_22_FUNC 1 << ((GPIO_22 % 10) * 3)
#define GPIO_26_FUNC 1 << ((GPIO_26 % 10) * 3)
/* mask bit */
#define FSEL_27_MASK 0b111 << ((GPIO_27 % 10) * 3)
#define FSEL_22_MASK 0b111 << ((GPIO_22 % 10) * 3)
#define FSEL_26_MASK 0b111 << ((GPIO_26 % 10) * 3)
#define GPIO_SET_FUNCTION_LEDS (GPIO_27_FUNC | GPIO_22_FUNC | GPIO_26_FUNC)
#define GPIO_MASK_ALL_LEDS (FSEL_27_MASK | FSEL_22_MASK | FSEL_26_MASK)
#define GPIO_SET_ALL_LEDS (GPIO_27_INDEX | GPIO_22_INDEX | GPIO_26_INDEX)
#define GPFSEL2 GPIO_BASE + 0x08
#define GPSET0 GPIO_BASE + 0x1C
#define GPCLR0 GPIO_BASE + 0x28
/* ioremap을 위한 전역변수 선언 */
static void __iomem *GPFSEL2_V;
static void __iomem *GPSET0_V;
static void __iomem *GPCLR0_V;
static ssize_t led_write(struct file *file, const char __user *buff, size_t count, loff_t *ppos)
{
const char *led_on = "on";
const char *led_off = "off";
struct led_dev *led_device;
pr_info("led_write is called.\n");
/*
file->private_data는 misc_register()함수로 등록한 struct 주소(miscdevice)가 저장된다
그러므로 data를 저장 할 struct(struct led_dev) 안에 struct miscdevice 를 넣어
data를 저장 할 struct(struct led_dev)를 찾아온다.
또한 misc 말고 다른 device도 동일한 방법으로 가능
*/
led_device = container_of(file->private_data,
struct led_dev, led_misc_device);
/*
echo "on"(off) > /dev/led??? 할경우 on(off) 문자열이 buff로 전달
*/
if(copy_from_user(led_device->led_value, buff, count)) {
pr_info("Bad copied value\n");
return -EFAULT;
}
led_device->led_value[count-1] = '\0';
pr_info("led_value from User Space: %s", led_device->led_value);
if(!strcmp(led_device->led_value, led_on)) {
iowrite32(led_device->led_mask, GPSET0_V);
}
else if (!strcmp(led_device->led_value, led_off)) {
iowrite32(led_device->led_mask, GPCLR0_V);
}
else {
pr_info("Bad value\n");
return -EINVAL;
}
pr_info("led_write is exit.\n");
return count;
}
static ssize_t led_read(struct file *file, char __user *buff, size_t count, loff_t *ppos)
{
struct led_dev *led_device;
pr_info("led_read enter\n");
led_device = container_of(file->private_data, struct led_dev, led_misc_device);
if(*ppos == 0){
if(copy_to_user(buff, &led_device->led_value, sizeof(led_device->led_value))){
pr_info("Failed to send to user space\n");
return -EFAULT;
}
/*ppos값을 읽은 data size 만큼 증가 */
*ppos+=sizeof(led_device->led_value);
return sizeof(led_device->led_value);
}
pr_info("led_read exit.\n");
return 0;
}
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.read = led_read,
.write = led_write,
};
static int led_probe(struct platform_device *pdev)
{
struct led_dev *led_device;
int ret;
char led_val[8] = "off\n";
pr_info("leds_probe enter\n");
/*led_dev struct를 동적 할당*/
led_device = devm_kzalloc(&pdev->dev, sizeof(struct led_dev), GFP_KERNEL);
/*
of_property_read_string(fron, match property, to)
device tree node의 property에서 label 값을 읽는다.
ledred {
compatible = "arrow,leds";
--->label = "ledred";
pinctrl-0 = <&led_pins>;
};
*/
of_property_read_string(pdev->dev.of_node, "label", &led_device->led_name);
led_device->led_misc_device.minor = MISC_DYNAMIC_MINOR;
led_device->led_misc_device.name = led_device->led_name;
led_device->led_misc_device.fops = &led_fops;
if (strcmp(led_device->led_name,"ledred") == 0) {
led_device->led_mask = GPIO_27_INDEX;
}
else if (strcmp(led_device->led_name,"ledgreen") == 0) {
led_device->led_mask = GPIO_22_INDEX;
}
else if (strcmp(led_device->led_name,"ledblue") == 0) {
led_device->led_mask = GPIO_26_INDEX;
}
else {
pr_info("device tree value is not match\n");
return -EINVAL;
}
memcpy(led_device->led_value, led_val, sizeof(led_val));
ret = misc_register(&led_device->led_misc_device);
if (ret)
return ret;
/*
pltform_device에 struct led_device를 저장한다
이것은 pdev만 알고 있다면 어디서근 led_device를 참조할 수 있다.
*/
platform_set_drvdata(pdev, led_device);
pr_info("leds_probe exit\n");
return 0;
}
static int led_remove(struct platform_device *pdev)
{
/*platform_set_drvdata 으로 저장했던 data를 가져온다.*/
struct led_dev *led_device = platform_get_drvdata(pdev);
pr_info("leds_remove enter\n");
misc_deregister(&led_device->led_misc_device);
pr_info("leds_remove exit\n");
return 0;
}
/*
device tree에 compatible = "arrow,leds" 로 할당된 property가 있는
갯수만큼 호출 ( 예를들어 3개가 있다면 probe는 3번 호출)
*/
static const struct of_device_id my_of_ids[] = {
{ .compatible = "arrow,leds"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);
static struct platform_driver led_platform_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "leds",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
static int led_init(void)
{
int ret_val;
u32 GPFSEL_read, GPFSEL_write;
pr_info("led_init enter\n");
/*
platform_driver_register() 함수는 Linux 커널에서 플랫폼 드라이버를 등록하는 역할을 합니다.
플랫폼 드라이버는 주로 시스템에 내장된 하드웨어 장치를 제어하는 데 사용되며,
이들 장치는 종종 시스템 버스에 연결되어 있습니다.
플랫폼 드라이버는 하드웨어와 시스템 사이에서 데이터를 교환하고,
하드웨어 동작을 제어하는 인터페이스를 제공합니다
*/
ret_val = platform_driver_register(&led_platform_driver);
if (ret_val !=0)
{
pr_err("platform value returned %d\n", ret_val);
return ret_val;
}
/*ioremap을 사용하여 레지스터와 가상주소를 mapping 한다*/
GPFSEL2_V = ioremap(GPFSEL2, sizeof(u32));
GPSET0_V = ioremap(GPSET0, sizeof(u32));
GPCLR0_V = ioremap(GPCLR0, sizeof(u32));
GPFSEL_read = ioread32(GPFSEL2_V);
/*
GPIO들을 OUTPUT으로 설정하기 위한 value를 만든다
*/
GPFSEL_write = (GPFSEL_read & ~GPIO_MASK_ALL_LEDS) |
(GPIO_SET_FUNCTION_LEDS & GPIO_MASK_ALL_LEDS);
/*iowrite32(write vlaue(from), 변경 할 register(to))*/
iowrite32(GPFSEL_write, GPFSEL2_V); /* gpio high */
iowrite32(GPIO_SET_ALL_LEDS, GPCLR0_V); /* gpio low */
pr_info("led_init exit\n");
return 0;
}
static void led_exit(void)
{
pr_info("led_exit enter\n");
iounmap(GPFSEL2_V);
iounmap(GPSET0_V);
iounmap(GPCLR0_V);
platform_driver_unregister(&led_platform_driver);
pr_info("led_exit exit\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
device tree 수정 -bcm2710-rpi-3-b.dts
&soc{
...
ledred {
compatible = "arrow,leds";
label = "ledred";
};
ledgreen {
compatible = "arrow,leds";
label = "ledgreen";
};
ledblue {
compatible = "arrow,leds";
label = "ledblue";
};
};
LED 연결시 GPIO 22,27,26에 연결
라즈베리파이 데이터 시트를 보면 peripherals address는 0x3F000000 ~ 0x3FFFFFFF 이며 이는 0x7E000000에 매핑되어 있습니다
BCM2837-ARM-Peripherals.pdf datasheet
perip
1.2.3 ARM physical addresses Physical addresses start at 0x00000000 for RAM.
• The ARM section of the RAM starts at 0x00000000.
• The VideoCore section of the RAM is mapped in only if the system is configured to support a memory mapped display (this is the common case). The VideoCore MMU maps the ARM physical address space to the bus address space seen by VideoCore (and VideoCore peripherals). The bus addresses for RAM are set up to map onto the uncached1 bus address range on the VideoCore starting at 0xC0000000.
Physical addresses range from 0x3F000000 to 0x3FFFFFFF for peripherals. The bus addresses for peripherals are set up to map onto the peripheral bus address range starting at 0x7E000000. Thus a peripheral advertised here at bus address 0x7Ennnnnn is available at physical address 0x3Fnnnnnn
address 0x7E20 0000 부터 gpio register는 시작하며 각 레지스터 마다 Field Name이 있으며 Field Name으로 레지스터에 설명되어 있다.
GPIO Function Select Registers은 3bit로 구성되어 있다
'000' = input
'001' = output
0~31bit 레지스터에는 10개의 레지스터 설정이 되어 있으므로 GPIO%10을 한다
하나의 GPIO설정에는 3bit로 되어 있으므로 '* 3'을 하여 GPIO 하나당 3bit를 쉬프트 한다
#define GPIO_22_FUNC 1 << ((GPIO_22 % 10) * 3)
GPCLR 레지스터를 1로 SET 해야 변경한 설정이 적용된다.
GPIO를 HIGH 설정한다
자료구조
device tree에 'compatible = "arrow,leds"';가 3개 있으므로 prob() 함수는 3번 호출이 된다.
device tree에 있는 node는 struct device.on_node에 저장되어 있다.
echo “on” -> /dev/led*
위 write 함수에서 첫번째 매개변수 struct file -> pivate_data 에는 misc_register() 함수에 인자값으로 넣어놓은 struct를 가리킨다.
--
platform_driver_register와 misc_register는 두 가지 다른 목적을 가지고 있습니다.
platform_driver_register는 플랫폼 드라이버를 커널에 등록하는 함수입니다. 이 함수는 플랫폼 드라이버의 probe 및 remove 메서드를 사용하여 플랫폼 장치가 시스템에 추가되거나 제거될 때 알림을 받습니다. 이러한 알림을 통해 드라이버는 장치에 대한 제어를 시작하거나 정지할 수 있습니다.
반면에 misc_register는 장치 파일을 /dev 디렉토리에 생성하는 함수입니다. 이를 통해 사용자 공간 프로그램은 장치 파일을 통해 드라이버와 통신할 수 있습니다. 이 함수는 또한 커널에 장치의 파일 작업(예: read, write, ioctl 등)을 등록합니다.
따라서 platform_driver_register는 하드웨어를 제어하기 위한 드라이버를 등록이며, misc_register는 사용자 공간과 통신하기 위한 장치 파일을 생성합니다. 두 가지 모두 필요하기 때문에 한 가지만 사용하는 것이 아니라 둘 다 사용하는 것입니다.
---
하드웨어와 통신할때 ioremap을 사용해서 컨트롤 하는걸로 보이는데 platform_driver_register 가 없어도 가능하지 않아?
->
ioremap 함수는 물리적 메모리 주소를 커널 공간의 가상 주소로 매핑합니다. 이를 통해 하드웨어 레지스터에 대한 직접적인 접근이 가능해집니다. 이것만으로도 하드웨어를 제어하는 것은 가능하긴 합니다.
그러나 platform_driver_register 함수를 사용하는 이유는 하드웨어에 대한 관리를 보다 체계적으로 하기 위함입니다. 플랫폼 드라이버는 하드웨어의 생명 주기를 관리하고, 드라이버 로드와 언로드, 에러 핸들링, 전력 관리 등의 복잡한 작업을 처리합니다.
또한, platform_driver_register는 디바이스 트리(device tree)와 연계하여 작동합니다. 디바이스 트리는 하드웨어 구성에 대한 정보를 저장하고, 이 정보를 바탕으로 커널이 드라이버와 장치를 연결하게 됩니다. platform_driver_register를 통해 드라이버를 등록하면, 이러한 디바이스 트리 메커니즘을 활용할 수 있게 됩니다. 이는 하드웨어 구성 변경에 대응하기 쉽게 해주며, 코드의 이식성을 높여줍니다.
따라서, 단순히 ioremap을 이용하여 하드웨어를 제어하는 것도 가능하지만, 실제 시스템에서는 플랫폼 드라이버를 사용하여 보다 체계적인 관리를 하는 것이 일반적입니다.
root@raspberrypi:/home/board# insmod 2_platform_driver_led.ko
root@raspberrypi:/home/board# dmesg
[ 32.978145] 2_platform_driver_led: loading out-of-tree module taints kernel.
[ 32.978646] led_init enter
[ 32.978906] leds_probe enter
[ 32.979088] leds_probe exit
[ 32.979222] leds_probe enter
[ 32.979334] leds_probe exit
[ 32.979450] leds_probe enter
[ 32.979549] leds_probe exit
[ 32.979724] led_init exit
root@raspberrypi:/home/board# echo on > /dev/ledred
[26958.151297] led_value from User Space: on
'device driver' 카테고리의 다른 글
charter device driver - 7. i2c platform driver 만들기 (0) | 2023.05.28 |
---|---|
charter device driver - 6. led_class_platform 만들기 (0) | 2023.05.26 |
charter device driver - 4. platform driver module 만들기 (0) | 2023.05.21 |
charter device driver - 3. misc driver 만들기 (0) | 2023.05.21 |
charter device driver - 2. char device drier 만들기 (0) | 2023.05.21 |
댓글