简言之,学习单片机编程,类似学习硬件驱动。
如何点亮一个LED灯,等同于驱动一个LED灯,再复杂一些可以控制LED灯亮灭,更复杂的是远程控制LED灯。
这些是也算是操作系统技术的一部分,所以课程名字又叫微机原理与接口技术等。
扩展案例如下:
看drivers文件夹下:
是不是很多眼熟的名称
linux/tree/master/drivers
I2C等,都是有的。
看leds
github.com/torvalds/linux/tree/master/drivers/leds
看看blink???
打好单片机基础,到能手写一个操作系统内核,稳稳点亮一个LED,讲真还是挺遥远的。
leds-lgm-sso.c
// SPDX-License-Identifier: GPL-2.0 /* * Intel Lightning Mountain SoC LED Serial Shift Output Controller driver * * Copyright (c) 2020 Intel Corporation. */ #include <linux/bitfield.h> #include <linux/clk.h> #include <linux/gpio/consumer.h> #include <linux/gpio/driver.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/leds.h> #include <linux/mfd/syscon.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/property.h> #include <linux/regmap.h> #include <linux/sizes.h> #include <linux/uaccess.h> #define SSO_DEV_NAME "lgm-sso" #define LED_BLINK_H8_0 0x0 #define LED_BLINK_H8_1 0x4 #define GET_FREQ_OFFSET(pin, src) (((pin) * 6) + ((src) * 2)) #define GET_SRC_OFFSET(pinc) (((pin) * 6) + 4) #define DUTY_CYCLE(x) (0x8 + ((x) * 4)) #define SSO_CON0 0x2B0 #define SSO_CON0_RZFL BIT(26) #define SSO_CON0_BLINK_R BIT(30) #define SSO_CON0_SWU BIT(31) #define SSO_CON1 0x2B4 #define SSO_CON1_FCDSC GENMASK(21, 20) /* Fixed Divider Shift Clock */ #define SSO_CON1_FPID GENMASK(24, 23) #define SSO_CON1_GPTD GENMASK(26, 25) #define SSO_CON1_US GENMASK(31, 30) #define SSO_CPU 0x2B8 #define SSO_CON2 0x2C4 #define SSO_CON3 0x2C8 /* Driver MACRO */ #define MAX_PIN_NUM_PER_BANK SZ_32 #define MAX_GROUP_NUM SZ_4 #define PINS_PER_GROUP SZ_8 #define FPID_FREQ_RANK_MAX SZ_4 #define SSO_LED_MAX_NUM SZ_32 #define MAX_FREQ_RANK 10 #define DEF_GPTC_CLK_RATE 200000000 #define SSO_DEF_BRIGHTNESS LED_HALF #define DATA_CLK_EDGE 0 /* 0-rising, 1-falling */ static const u32 freq_div_tbl[] = {4000, 2000, 1000, 800}; static const int freq_tbl[] = {2, 4, 8, 10, 50000, 100000, 200000, 250000}; static const int shift_clk_freq_tbl[] = {25000000, 12500000, 6250000, 3125000}; /* * Update Source to update the SOUTs * SW - Software has to update the SWU bit * GPTC - General Purpose timer is used as clock source * FPID - Divided FSC clock (FPID) is used as clock source */ enum { US_SW = 0, US_GPTC = 1, US_FPID = 2 }; enum { MAX_FPID_FREQ_RANK = 5, /* 1 to 4 */ MAX_GPTC_FREQ_RANK = 9, /* 5 to 8 */ MAX_GPTC_HS_FREQ_RANK = 10, /* 9 to 10 */ }; enum { LED_GRP0_PIN_MAX = 24, LED_GRP1_PIN_MAX = 29, LED_GRP2_PIN_MAX = 32, }; enum { LED_GRP0_0_23, LED_GRP1_24_28, LED_GRP2_29_31, LED_GROUP_MAX, }; enum { CLK_SRC_FPID = 0, CLK_SRC_GPTC = 1, CLK_SRC_GPTC_HS = 2, }; struct sso_led_priv; struct sso_led_desc { const char *name; const char *default_trigger; unsigned int brightness; unsigned int blink_rate; unsigned int retain_state_suspended:1; unsigned int retain_state_shutdown:1; unsigned int panic_indicator:1; unsigned int hw_blink:1; unsigned int hw_trig:1; unsigned int blinking:1; int freq_idx; u32 pin; }; struct sso_led { struct list_head list; struct led_classdev cdev; struct gpio_desc *gpiod; struct sso_led_desc desc; struct sso_led_priv *priv; }; struct sso_gpio { struct gpio_chip chip; int shift_clk_freq; int edge; int freq; u32 pins; u32 alloc_bitmap; }; struct sso_led_priv { struct regmap *mmap; struct device *dev; struct platform_device *pdev; struct clk_bulk_data clocks[2]; u32 fpid_clkrate; u32 gptc_clkrate; u32 freq[MAX_FREQ_RANK]; struct list_head led_list; struct sso_gpio gpio; }; static int sso_get_blink_rate_idx(struct sso_led_priv *priv, u32 rate) { int i; for (i = 0; i < MAX_FREQ_RANK; i++) { if (rate <= priv->freq[i]) return i; } return -1; } static unsigned int sso_led_pin_to_group(u32 pin) { if (pin < LED_GRP0_PIN_MAX) return LED_GRP0_0_23; else if (pin < LED_GRP1_PIN_MAX) return LED_GRP1_24_28; else return LED_GRP2_29_31; } static u32 sso_led_get_freq_src(int freq_idx) { if (freq_idx < MAX_FPID_FREQ_RANK) return CLK_SRC_FPID; else if (freq_idx < MAX_GPTC_FREQ_RANK) return CLK_SRC_GPTC; else return CLK_SRC_GPTC_HS; } static u32 sso_led_pin_blink_off(u32 pin, unsigned int group) { if (group == LED_GRP2_29_31) return pin - LED_GRP1_PIN_MAX; else if (group == LED_GRP1_24_28) return pin - LED_GRP0_PIN_MAX; else /* led 0 - 23 in led 32 location */ return SSO_LED_MAX_NUM - LED_GRP1_PIN_MAX; } static struct sso_led *cdev_to_sso_led_data(struct led_classdev *led_cdev) { return container_of(led_cdev, struct sso_led, cdev); } static void sso_led_freq_set(struct sso_led_priv *priv, u32 pin, int freq_idx) { u32 reg, off, freq_src, val_freq; u32 low, high, val; unsigned int group; if (!freq_idx) return; group = sso_led_pin_to_group(pin); freq_src = sso_led_get_freq_src(freq_idx); off = sso_led_pin_blink_off(pin, group); if (group == LED_GRP0_0_23) return; else if (group == LED_GRP1_24_28) reg = LED_BLINK_H8_0; else reg = LED_BLINK_H8_1; if (freq_src == CLK_SRC_FPID) val_freq = freq_idx - 1; else if (freq_src == CLK_SRC_GPTC) val_freq = freq_idx - MAX_FPID_FREQ_RANK; /* set blink rate idx */ if (freq_src != CLK_SRC_GPTC_HS) { low = GET_FREQ_OFFSET(off, freq_src); high = low + 2; val = val_freq << high; regmap_update_bits(priv->mmap, reg, GENMASK(high, low), val); } /* select clock source */ low = GET_SRC_OFFSET(off); high = low + 2; val = freq_src << high; regmap_update_bits(priv->mmap, reg, GENMASK(high, low), val); } static void sso_led_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct sso_led_priv *priv; struct sso_led_desc *desc; struct sso_led *led; int val; led = cdev_to_sso_led_data(led_cdev); priv = led->priv; desc = &led->desc; desc->brightness = brightness; regmap_write(priv->mmap, DUTY_CYCLE(desc->pin), brightness); if (brightness == LED_OFF) val = 0; else val = 1; /* HW blink off */ if (desc->hw_blink && !val && desc->blinking) { desc->blinking = 0; regmap_update_bits(priv->mmap, SSO_CON2, BIT(desc->pin), 0); } else if (desc->hw_blink && val && !desc->blinking) { desc->blinking = 1; regmap_update_bits(priv->mmap, SSO_CON2, BIT(desc->pin), 1 << desc->pin); } if (!desc->hw_trig) gpiod_set_value(led->gpiod, val); } static enum led_brightness sso_led_brightness_get(struct led_classdev *led_cdev) { struct sso_led *led = cdev_to_sso_led_data(led_cdev); return (enum led_brightness)led->desc.brightness; } static int delay_to_freq_idx(struct sso_led *led, unsigned long *delay_on, unsigned long *delay_off) { struct sso_led_priv *priv = led->priv; unsigned long delay; int freq_idx; u32 freq; if (!*delay_on && !*delay_off) { *delay_on = *delay_off = (1000 / priv->freq[0]) / 2; return 0; } delay = *delay_on + *delay_off; freq = 1000 / delay; freq_idx = sso_get_blink_rate_idx(priv, freq); if (freq_idx == -1) freq_idx = MAX_FREQ_RANK - 1; delay = 1000 / priv->freq[freq_idx]; *delay_on = *delay_off = delay / 2; if (!*delay_on) *delay_on = *delay_off = 1; return freq_idx; } static int sso_led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { struct sso_led_priv *priv; struct sso_led *led; int freq_idx; led = cdev_to_sso_led_data(led_cdev); priv = led->priv; freq_idx = delay_to_freq_idx(led, delay_on, delay_off); sso_led_freq_set(priv, led->desc.pin, freq_idx); regmap_update_bits(priv->mmap, SSO_CON2, BIT(led->desc.pin), 1 << led->desc.pin); led->desc.freq_idx = freq_idx; led->desc.blink_rate = priv->freq[freq_idx]; led->desc.blinking = 1; return 1; } static void sso_led_hw_cfg(struct sso_led_priv *priv, struct sso_led *led) { struct sso_led_desc *desc = &led->desc; /* set freq */ if (desc->hw_blink) { sso_led_freq_set(priv, desc->pin, desc->freq_idx); regmap_update_bits(priv->mmap, SSO_CON2, BIT(desc->pin), 1 << desc->pin); } if (desc->hw_trig) regmap_update_bits(priv->mmap, SSO_CON3, BIT(desc->pin), 1 << desc->pin); /* set brightness */ regmap_write(priv->mmap, DUTY_CYCLE(desc->pin), desc->brightness); /* enable output */ if (!desc->hw_trig && desc->brightness) gpiod_set_value(led->gpiod, 1); } static int sso_create_led(struct sso_led_priv *priv, struct sso_led *led, struct fwnode_handle *child) { struct sso_led_desc *desc = &led->desc; struct led_init_data init_data; int err; init_data.fwnode = child; init_data.devicename = SSO_DEV_NAME; init_data.default_label = ":"; led->cdev.default_trigger = desc->default_trigger; led->cdev.brightness_set = sso_led_brightness_set; led->cdev.brightness_get = sso_led_brightness_get; led->cdev.brightness = desc->brightness; led->cdev.max_brightness = LED_FULL; if (desc->retain_state_shutdown) led->cdev.flags |= LED_RETAIN_AT_SHUTDOWN; if (desc->retain_state_suspended) led->cdev.flags |= LED_CORE_SUSPENDRESUME; if (desc->panic_indicator) led->cdev.flags |= LED_PANIC_INDICATOR; if (desc->hw_blink) led->cdev.blink_set = sso_led_blink_set; sso_led_hw_cfg(priv, led); err = devm_led_classdev_register_ext(priv->dev, &led->cdev, &init_data); if (err) return err; list_add(&led->list, &priv->led_list); return 0; } static void sso_init_freq(struct sso_led_priv *priv) { int i; priv->freq[0] = 0; for (i = 1; i < MAX_FREQ_RANK; i++) { if (i < MAX_FPID_FREQ_RANK) { priv->freq[i] = priv->fpid_clkrate / freq_div_tbl[i - 1]; } else if (i < MAX_GPTC_FREQ_RANK) { priv->freq[i] = priv->gptc_clkrate / freq_div_tbl[i - MAX_FPID_FREQ_RANK]; } else if (i < MAX_GPTC_HS_FREQ_RANK) { priv->freq[i] = priv->gptc_clkrate; } } } static int sso_gpio_request(struct gpio_chip *chip, unsigned int offset) { struct sso_led_priv *priv = gpiochip_get_data(chip); if (priv->gpio.alloc_bitmap & BIT(offset)) return -EINVAL; priv->gpio.alloc_bitmap |= BIT(offset); regmap_write(priv->mmap, DUTY_CYCLE(offset), 0xFF); return 0; } static void sso_gpio_free(struct gpio_chip *chip, unsigned int offset) { struct sso_led_priv *priv = gpiochip_get_data(chip); priv->gpio.alloc_bitmap &= ~BIT(offset); regmap_write(priv->mmap, DUTY_CYCLE(offset), 0x0); } static int sso_gpio_get_dir(struct gpio_chip *chip, unsigned int offset) { return GPIO_LINE_DIRECTION_OUT; } static int sso_gpio_dir_out(struct gpio_chip *chip, unsigned int offset, int value) { struct sso_led_priv *priv = gpiochip_get_data(chip); bool bit = !!value; regmap_update_bits(priv->mmap, SSO_CPU, BIT(offset), bit << offset); if (!priv->gpio.freq) regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_SWU, SSO_CON0_SWU); return 0; } static int sso_gpio_get(struct gpio_chip *chip, unsigned int offset) { struct sso_led_priv *priv = gpiochip_get_data(chip); u32 reg_val; regmap_read(priv->mmap, SSO_CPU, ®_val); return !!(reg_val & BIT(offset)); } static void sso_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) { struct sso_led_priv *priv = gpiochip_get_data(chip); regmap_update_bits(priv->mmap, SSO_CPU, BIT(offset), value << offset); if (!priv->gpio.freq) regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_SWU, SSO_CON0_SWU); } static int sso_gpio_gc_init(struct device *dev, struct sso_led_priv *priv) { struct gpio_chip *gc = &priv->gpio.chip; gc->request = sso_gpio_request; gc->free = sso_gpio_free; gc->get_direction = sso_gpio_get_dir; gc->direction_output = sso_gpio_dir_out; gc->get = sso_gpio_get; gc->set = sso_gpio_set; gc->label = "lgm-sso"; gc->base = -1; /* To exclude pins from control, use "gpio-reserved-ranges" */ gc->ngpio = priv->gpio.pins; gc->parent = dev; gc->owner = THIS_MODULE; gc->of_node = dev->of_node; return devm_gpiochip_add_data(dev, gc, priv); } static int sso_gpio_get_freq_idx(int freq) { int idx; for (idx = 0; idx < ARRAY_SIZE(freq_tbl); idx++) { if (freq <= freq_tbl[idx]) return idx; } return -1; } static void sso_register_shift_clk(struct sso_led_priv *priv) { int idx, size = ARRAY_SIZE(shift_clk_freq_tbl); u32 val = 0; for (idx = 0; idx < size; idx++) { if (shift_clk_freq_tbl[idx] <= priv->gpio.shift_clk_freq) { val = idx; break; } } if (idx == size) dev_warn(priv->dev, "%s: Invalid freq %d\n", __func__, priv->gpio.shift_clk_freq); regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_FCDSC, FIELD_PREP(SSO_CON1_FCDSC, val)); } static int sso_gpio_freq_set(struct sso_led_priv *priv) { int freq_idx; u32 val; freq_idx = sso_gpio_get_freq_idx(priv->gpio.freq); if (freq_idx == -1) freq_idx = ARRAY_SIZE(freq_tbl) - 1; val = freq_idx % FPID_FREQ_RANK_MAX; if (!priv->gpio.freq) { regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_BLINK_R, 0); regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_US, FIELD_PREP(SSO_CON1_US, US_SW)); } else if (freq_idx < FPID_FREQ_RANK_MAX) { regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_BLINK_R, SSO_CON0_BLINK_R); regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_US, FIELD_PREP(SSO_CON1_US, US_FPID)); regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_FPID, FIELD_PREP(SSO_CON1_FPID, val)); } else { regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_BLINK_R, SSO_CON0_BLINK_R); regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_US, FIELD_PREP(SSO_CON1_US, US_GPTC)); regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_GPTD, FIELD_PREP(SSO_CON1_GPTD, val)); } return 0; } static int sso_gpio_hw_init(struct sso_led_priv *priv) { u32 activate; int i, err; /* Clear all duty cycles */ for (i = 0; i < priv->gpio.pins; i++) { err = regmap_write(priv->mmap, DUTY_CYCLE(i), 0); if (err) return err; } /* 4 groups for total 32 pins */ for (i = 1; i <= MAX_GROUP_NUM; i++) { activate = !!(i * PINS_PER_GROUP <= priv->gpio.pins || priv->gpio.pins > (i - 1) * PINS_PER_GROUP); err = regmap_update_bits(priv->mmap, SSO_CON1, BIT(i - 1), activate << (i - 1)); if (err) return err; } /* NO HW directly controlled pin by default */ err = regmap_write(priv->mmap, SSO_CON3, 0); if (err) return err; /* NO BLINK for all pins */ err = regmap_write(priv->mmap, SSO_CON2, 0); if (err) return err; /* OUTPUT 0 by default */ err = regmap_write(priv->mmap, SSO_CPU, 0); if (err) return err; /* update edge */ err = regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_RZFL, FIELD_PREP(SSO_CON0_RZFL, priv->gpio.edge)); if (err) return err; /* Set GPIO update rate */ sso_gpio_freq_set(priv); /* Register shift clock */ sso_register_shift_clk(priv); return 0; } static void sso_led_shutdown(struct sso_led *led) { struct sso_led_priv *priv = led->priv; /* unregister led */ devm_led_classdev_unregister(priv->dev, &led->cdev); /* clear HW control bit */ if (led->desc.hw_trig) regmap_update_bits(priv->mmap, SSO_CON3, BIT(led->desc.pin), 0); led->priv = NULL; } static int __sso_led_dt_parse(struct sso_led_priv *priv, struct fwnode_handle *fw_ssoled) { struct fwnode_handle *fwnode_child; struct device *dev = priv->dev; struct sso_led_desc *desc; struct sso_led *led; const char *tmp; u32 prop; int ret; fwnode_for_each_child_node(fw_ssoled, fwnode_child) { led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); if (!led) { ret = -ENOMEM; goto __dt_err; } INIT_LIST_HEAD(&led->list); led->priv = priv; desc = &led->desc; led->gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, fwnode_child, GPIOD_ASIS, NULL); if (IS_ERR(led->gpiod)) { ret = dev_err_probe(dev, PTR_ERR(led->gpiod), "led: get gpio fail!\n"); goto __dt_err; } fwnode_property_read_string(fwnode_child, "linux,default-trigger", &desc->default_trigger); if (fwnode_property_present(fwnode_child, "retain-state-suspended")) desc->retain_state_suspended = 1; if (fwnode_property_present(fwnode_child, "retain-state-shutdown")) desc->retain_state_shutdown = 1; if (fwnode_property_present(fwnode_child, "panic-indicator")) desc->panic_indicator = 1; ret = fwnode_property_read_u32(fwnode_child, "reg", &prop); if (ret) goto __dt_err; if (prop >= SSO_LED_MAX_NUM) { dev_err(dev, "invalid LED pin:%u\n", prop); ret = -EINVAL; goto __dt_err; } desc->pin = prop; if (fwnode_property_present(fwnode_child, "intel,sso-hw-blink")) desc->hw_blink = 1; desc->hw_trig = fwnode_property_read_bool(fwnode_child, "intel,sso-hw-trigger"); if (desc->hw_trig) { desc->default_trigger = NULL; desc->retain_state_shutdown = 0; desc->retain_state_suspended = 0; desc->panic_indicator = 0; desc->hw_blink = 0; } if (fwnode_property_read_u32(fwnode_child, "intel,sso-blink-rate-hz", &prop)) { /* default first freq rate */ desc->freq_idx = 0; desc->blink_rate = priv->freq[desc->freq_idx]; } else { desc->freq_idx = sso_get_blink_rate_idx(priv, prop); if (desc->freq_idx == -1) desc->freq_idx = MAX_FREQ_RANK - 1; desc->blink_rate = priv->freq[desc->freq_idx]; } if (!fwnode_property_read_string(fwnode_child, "default-state", &tmp)) { if (!strcmp(tmp, "on")) desc->brightness = LED_FULL; } ret = sso_create_led(priv, led, fwnode_child); if (ret) goto __dt_err; } return 0; __dt_err: fwnode_handle_put(fwnode_child); /* unregister leds */ list_for_each_entry(led, &priv->led_list, list) sso_led_shutdown(led); return ret; } static int sso_led_dt_parse(struct sso_led_priv *priv) { struct fwnode_handle *fwnode = dev_fwnode(priv->dev); struct fwnode_handle *fw_ssoled; struct device *dev = priv->dev; int count; int ret; count = device_get_child_node_count(dev); if (!count) return 0; fw_ssoled = fwnode_get_named_child_node(fwnode, "ssoled"); if (fw_ssoled) { ret = __sso_led_dt_parse(priv, fw_ssoled); fwnode_handle_put(fw_ssoled); if (ret) return ret; } return 0; } static int sso_probe_gpios(struct sso_led_priv *priv) { struct device *dev = priv->dev; int ret; if (device_property_read_u32(dev, "ngpios", &priv->gpio.pins)) priv->gpio.pins = MAX_PIN_NUM_PER_BANK; if (priv->gpio.pins > MAX_PIN_NUM_PER_BANK) return -EINVAL; if (device_property_read_u32(dev, "intel,sso-update-rate-hz", &priv->gpio.freq)) priv->gpio.freq = 0; priv->gpio.edge = DATA_CLK_EDGE; priv->gpio.shift_clk_freq = -1; ret = sso_gpio_hw_init(priv); if (ret) return ret; return sso_gpio_gc_init(dev, priv); } static void sso_clock_disable_unprepare(void *data) { struct sso_led_priv *priv = data; clk_bulk_disable_unprepare(ARRAY_SIZE(priv->clocks), priv->clocks); } static int intel_sso_led_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct sso_led_priv *priv; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->pdev = pdev; priv->dev = dev; /* gate clock */ priv->clocks[0].id = "sso"; /* fpid clock */ priv->clocks[1].id = "fpid"; ret = devm_clk_bulk_get(dev, ARRAY_SIZE(priv->clocks), priv->clocks); if (ret) { dev_err(dev, "Getting clocks failed!\n"); return ret; } ret = clk_bulk_prepare_enable(ARRAY_SIZE(priv->clocks), priv->clocks); if (ret) { dev_err(dev, "Failed to prepare and enable clocks!\n"); return ret; } ret = devm_add_action_or_reset(dev, sso_clock_disable_unprepare, priv); if (ret) return ret; priv->fpid_clkrate = clk_get_rate(priv->clocks[1].clk); priv->mmap = syscon_node_to_regmap(dev->of_node); priv->mmap = syscon_node_to_regmap(dev->of_node); if (IS_ERR(priv->mmap)) { dev_err(dev, "Failed to map iomem!\n"); return PTR_ERR(priv->mmap); } ret = sso_probe_gpios(priv); if (ret) { regmap_exit(priv->mmap); return ret; } INIT_LIST_HEAD(&priv->led_list); platform_set_drvdata(pdev, priv); sso_init_freq(priv); priv->gptc_clkrate = DEF_GPTC_CLK_RATE; ret = sso_led_dt_parse(priv); if (ret) { regmap_exit(priv->mmap); return ret; } dev_info(priv->dev, "sso LED init success!\n"); return 0; } static int intel_sso_led_remove(struct platform_device *pdev) { struct sso_led_priv *priv; struct sso_led *led, *n; priv = platform_get_drvdata(pdev); list_for_each_entry_safe(led, n, &priv->led_list, list) { list_del(&led->list); sso_led_shutdown(led); } regmap_exit(priv->mmap); return 0; } static const struct of_device_id of_sso_led_match[] = { { .compatible = "intel,lgm-ssoled" }, {} }; MODULE_DEVICE_TABLE(of, of_sso_led_match); static struct platform_driver intel_sso_led_driver = { .probe = intel_sso_led_probe, .remove = intel_sso_led_remove, .driver = { .name = "lgm-ssoled", .of_match_table = of_sso_led_match, }, }; module_platform_driver(intel_sso_led_driver); MODULE_DESCRIPTION("Intel SSO LED/GPIO driver"); MODULE_LICENSE("GPL v2");