drivers: flash: remove W25QXXDV driver

The spi_flash_w25qxxdv driver has been superseded by the generic
spi_nor driver for over a year.  The only non-refactoring change to
the W25Q driver in the last 18 months was done to support a backport
to 1.14.

All devices supported by spi_flash_w25qxxdv driver are expected to be
supported by the spi_nor driver, using the standard `jedec,spi-nor`
devicetree compatible.  No in-tree devicetree files make use of this
driver.

Remove the confusion about which driver to select by removing the
unmaintained redundant driver.

Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
This commit is contained in:
Peter Bigot 2020-08-26 07:45:28 -05:00 committed by Carles Cufí
commit 77aa89bcb5
10 changed files with 6 additions and 691 deletions

View file

@ -197,6 +197,11 @@ Drivers and Sensors
* Flash
* The driver selected by ``CONFIG_SPI_FLASH_W25QXXDV`` has been
removed as it is unmaintained and all its functionality is available
through ``CONFIG_SPI_NOR``. Out of tree uses should convert to the
supported driver using the ``jedec,spi-nor`` compatible.
* GPIO

View file

@ -6,7 +6,6 @@ zephyr_library_sources_ifdef(CONFIG_SPI_NOR spi_nor.c)
zephyr_library_sources_ifdef(CONFIG_NORDIC_QSPI_NOR nrf_qspi_nor.c)
zephyr_library_sources_ifdef(CONFIG_FLASH_SIMULATOR flash_simulator.c)
zephyr_library_sources_ifdef(CONFIG_SPI_FLASH_AT45 spi_flash_at45.c)
zephyr_library_sources_ifdef(CONFIG_SPI_FLASH_W25QXXDV spi_flash_w25qxxdv.c)
zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_NRF soc_flash_nrf.c)
zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_MCUX soc_flash_mcux.c)
zephyr_library_sources_ifdef(CONFIG_FLASH_PAGE_LAYOUT flash_page_layout.c)

View file

@ -76,8 +76,6 @@ source "drivers/flash/Kconfig.sam0"
source "drivers/flash/Kconfig.sam"
source "drivers/flash/Kconfig.w25qxxdv"
source "drivers/flash/Kconfig.simulator"
source "drivers/flash/Kconfig.rv32m1"

View file

@ -1,50 +0,0 @@
# Copyright (c) 2018 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
menuconfig SPI_FLASH_W25QXXDV
bool "SPI NOR Flash Winbond W25QXXDV"
select FLASH_HAS_DRIVER_ENABLED
depends on SPI
if SPI_FLASH_W25QXXDV
config SPI_FLASH_W25QXXDV_DRV_NAME
string "SPI flash device name"
default "W25QXXDV"
config SPI_FLASH_W25QXXDV_INIT_PRIORITY
int
default 80
help
Device driver initialization priority.
Device is connected to SPI bus, it has to
be initialized after SPI driver.
config SPI_FLASH_W25QXXDV_GPIO_CS_WAIT_DELAY
int "Delay time in us"
default 0
help
This is the wait delay (in us) to allow for CS switching to take effect
config SPI_FLASH_W25QXXDV_FLASH_SIZE
int "Flash size in bytes"
default 2097152
help
This is the flash capacity in bytes.
config SPI_FLASH_W25QXXDV_DEVICE_ID
hex "Device ID in hex"
default 0x00ef4015
help
This is the device ID of the flash chip to use, which is 0x00ef4015 for
the W25QXXDV
config SPI_FLASH_W25QXXDV_PAGE_PROGRAM_SIZE
int "Page Program Size in bytes"
default 256
help
This is the maximum size of a page program operation. Writing data
over this page boundary will split the write operation into two
pages.
endif # SPI_FLASH_W25QXXDV

View file

@ -1,470 +0,0 @@
/*
* Copyright (c) 2016 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT winbond_w25q16
#include <errno.h>
#include <drivers/flash.h>
#include <drivers/spi.h>
#include <init.h>
#include <string.h>
#include "spi_flash_w25qxxdv_defs.h"
#include "spi_flash_w25qxxdv.h"
#include "flash_priv.h"
#if defined(CONFIG_MULTITHREADING)
#define SYNC_INIT() k_sem_init( \
&((struct spi_flash_data *)dev->data)->sem, 1, UINT_MAX)
#define SYNC_LOCK() k_sem_take(&driver_data->sem, K_FOREVER)
#define SYNC_UNLOCK() k_sem_give(&driver_data->sem)
#else
#define SYNC_INIT()
#define SYNC_LOCK()
#define SYNC_UNLOCK()
#endif
static const struct flash_parameters flash_wb_parameters = {
.write_block_size = 1,
.erase_value = 0xff,
};
static int spi_flash_wb_access(struct spi_flash_data *ctx,
uint8_t cmd, bool addressed, off_t offset,
void *data, size_t length, bool write)
{
uint8_t access[4];
struct spi_buf buf[2] = {
{
.buf = access
},
{
.buf = data,
.len = length
}
};
struct spi_buf_set tx = {
.buffers = buf,
};
access[0] = cmd;
if (addressed) {
access[1] = (uint8_t) (offset >> 16);
access[2] = (uint8_t) (offset >> 8);
access[3] = (uint8_t) offset;
buf[0].len = 4;
} else {
buf[0].len = 1;
}
tx.count = length ? 2 : 1;
if (!write) {
const struct spi_buf_set rx = {
.buffers = buf,
.count = 2
};
return spi_transceive(ctx->spi, &ctx->spi_cfg, &tx, &rx);
}
return spi_write(ctx->spi, &ctx->spi_cfg, &tx);
}
static inline int spi_flash_wb_id(struct device *dev)
{
struct spi_flash_data *const driver_data = dev->data;
uint32_t temp_data;
uint8_t buf[3];
if (spi_flash_wb_access(driver_data, W25QXXDV_CMD_RDID,
false, 0, buf, 3, false) != 0) {
return -EIO;
}
temp_data = ((uint32_t) buf[0]) << 16;
temp_data |= ((uint32_t) buf[1]) << 8;
temp_data |= (uint32_t) buf[2];
if (temp_data != CONFIG_SPI_FLASH_W25QXXDV_DEVICE_ID) {
return -ENODEV;
}
return 0;
}
static uint8_t spi_flash_wb_reg_read(struct device *dev, uint8_t reg)
{
struct spi_flash_data *const driver_data = dev->data;
if (spi_flash_wb_access(driver_data, reg,
false, 0, &reg, 1, false)) {
return 0;
}
return reg;
}
static inline void wait_for_flash_idle(struct device *dev)
{
uint8_t reg;
do {
reg = spi_flash_wb_reg_read(dev, W25QXXDV_CMD_RDSR);
} while (reg & W25QXXDV_WIP_BIT);
}
static int spi_flash_wb_reg_write(struct device *dev, uint8_t reg)
{
struct spi_flash_data *const driver_data = dev->data;
if (spi_flash_wb_access(driver_data, reg, false, 0,
NULL, 0, true) != 0) {
return -EIO;
}
return 0;
}
static int spi_flash_wb_read(struct device *dev, off_t offset, void *data,
size_t len)
{
struct spi_flash_data *const driver_data = dev->data;
int ret;
if (offset < 0) {
return -ENODEV;
}
SYNC_LOCK();
wait_for_flash_idle(dev);
ret = spi_flash_wb_access(driver_data, W25QXXDV_CMD_READ,
true, offset, data, len, false);
SYNC_UNLOCK();
return ret;
}
static int spi_flash_wb_write_protection_set_with_lock(struct device *dev,
bool enable, bool lock)
{
struct spi_flash_data *const driver_data = dev->data;
uint8_t reg = 0U;
int ret;
if (lock) {
SYNC_LOCK();
}
wait_for_flash_idle(dev);
if (enable) {
reg = W25QXXDV_CMD_WRDI;
} else {
reg = W25QXXDV_CMD_WREN;
}
ret = spi_flash_wb_reg_write(dev, reg);
if (lock) {
SYNC_UNLOCK();
}
return ret;
}
static int spi_flash_wb_write_protection_set(struct device *dev, bool enable)
{
return spi_flash_wb_write_protection_set_with_lock(dev, enable, true);
}
static int spi_flash_wb_program_page(struct device *dev, off_t offset,
const void *data, size_t len)
{
uint8_t reg;
struct spi_flash_data *const driver_data = dev->data;
__ASSERT(len <= CONFIG_SPI_FLASH_W25QXXDV_PAGE_PROGRAM_SIZE,
"Maximum length is %d for page programming (actual:%d)",
CONFIG_SPI_FLASH_W25QXXDV_PAGE_PROGRAM_SIZE, len);
wait_for_flash_idle(dev);
reg = spi_flash_wb_reg_read(dev, W25QXXDV_CMD_RDSR);
if (!(reg & W25QXXDV_WEL_BIT)) {
return -EIO;
}
wait_for_flash_idle(dev);
/* Assume write protection has been disabled. Note that w25qxxdv
* flash automatically turns on write protection at the completion
* of each write or erase transaction.
*/
return spi_flash_wb_access(driver_data, W25QXXDV_CMD_PP,
true, offset, (void *)data, len, true);
}
static int spi_flash_wb_write(struct device *dev, off_t offset,
const void *data, size_t len)
{
int ret;
off_t page_offset;
/* Cast `data` to prevent `void*` arithmetic */
const uint8_t *data_ptr = data;
struct spi_flash_data *const driver_data = dev->data;
if (offset < 0) {
return -ENOTSUP;
}
SYNC_LOCK();
/* Calculate the offset in the first page we write */
page_offset = offset % CONFIG_SPI_FLASH_W25QXXDV_PAGE_PROGRAM_SIZE;
/*
* Write all data that does not fit into a single programmable page.
* By doing this logic, we can safely disable lock protection in
* between pages as in case the user did not disable protection then
* it will fail on the first write.
*/
while ((page_offset + len) >
CONFIG_SPI_FLASH_W25QXXDV_PAGE_PROGRAM_SIZE) {
size_t len_to_write_in_page =
CONFIG_SPI_FLASH_W25QXXDV_PAGE_PROGRAM_SIZE -
page_offset;
ret = spi_flash_wb_program_page(dev, offset,
data_ptr, len_to_write_in_page);
if (ret) {
goto end;
}
ret = spi_flash_wb_write_protection_set_with_lock(dev,
false, false);
if (ret) {
goto end;
}
len -= len_to_write_in_page;
offset += len_to_write_in_page;
data_ptr += len_to_write_in_page;
/*
* For the subsequent pages we always start at the beginning
* of a page
*/
page_offset = 0;
}
ret = spi_flash_wb_program_page(dev, offset, data_ptr, len);
end:
SYNC_UNLOCK();
return ret;
}
static inline int spi_flash_wb_erase_internal(struct device *dev,
off_t offset, size_t size)
{
struct spi_flash_data *const driver_data = dev->data;
bool need_offset = true;
uint8_t erase_opcode;
if (offset < 0) {
return -ENOTSUP;
}
wait_for_flash_idle(dev);
/* write enable */
spi_flash_wb_reg_write(dev, W25QXXDV_CMD_WREN);
wait_for_flash_idle(dev);
switch (size) {
case W25QXXDV_SECTOR_SIZE:
erase_opcode = W25QXXDV_CMD_SE;
break;
case W25QXXDV_BLOCK32K_SIZE:
erase_opcode = W25QXXDV_CMD_BE32K;
break;
case W25QXXDV_BLOCK_SIZE:
erase_opcode = W25QXXDV_CMD_BE;
break;
case CONFIG_SPI_FLASH_W25QXXDV_FLASH_SIZE:
erase_opcode = W25QXXDV_CMD_CE;
need_offset = false;
break;
default:
return -EIO;
}
/* Assume write protection has been disabled. Note that w25qxxdv
* flash automatically turns on write protection at the completion
* of each write or erase transaction.
*/
return spi_flash_wb_access(driver_data, erase_opcode,
need_offset, offset, NULL, 0, true);
}
static int spi_flash_wb_erase(struct device *dev, off_t offset, size_t size)
{
struct spi_flash_data *const driver_data = dev->data;
int ret = 0;
uint32_t new_offset = offset;
uint32_t size_remaining = size;
uint8_t reg;
if ((offset < 0) || ((offset & W25QXXDV_SECTOR_MASK) != 0) ||
((size + offset) > CONFIG_SPI_FLASH_W25QXXDV_FLASH_SIZE) ||
((size & W25QXXDV_SECTOR_MASK) != 0)) {
return -ENODEV;
}
SYNC_LOCK();
reg = spi_flash_wb_reg_read(dev, W25QXXDV_CMD_RDSR);
if (!(reg & W25QXXDV_WEL_BIT)) {
SYNC_UNLOCK();
return -EIO;
}
while ((size_remaining >= W25QXXDV_SECTOR_SIZE) && (ret == 0)) {
if (size_remaining == CONFIG_SPI_FLASH_W25QXXDV_FLASH_SIZE) {
ret = spi_flash_wb_erase_internal(dev, offset, size);
break;
}
if ((size_remaining >= W25QXXDV_BLOCK_SIZE) &&
((new_offset & (W25QXXDV_BLOCK_SIZE - 1)) == 0)) {
ret = spi_flash_wb_erase_internal(dev, new_offset,
W25QXXDV_BLOCK_SIZE);
new_offset += W25QXXDV_BLOCK_SIZE;
size_remaining -= W25QXXDV_BLOCK_SIZE;
continue;
}
if ((size_remaining >= W25QXXDV_BLOCK32K_SIZE) &&
((new_offset & (W25QXXDV_BLOCK32K_SIZE - 1)) == 0)) {
ret = spi_flash_wb_erase_internal(dev, new_offset,
W25QXXDV_BLOCK32K_SIZE);
new_offset += W25QXXDV_BLOCK32K_SIZE;
size_remaining -= W25QXXDV_BLOCK32K_SIZE;
continue;
}
if (size_remaining >= W25QXXDV_SECTOR_SIZE) {
ret = spi_flash_wb_erase_internal(dev, new_offset,
W25QXXDV_SECTOR_SIZE);
new_offset += W25QXXDV_SECTOR_SIZE;
size_remaining -= W25QXXDV_SECTOR_SIZE;
continue;
}
}
SYNC_UNLOCK();
return ret;
}
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
static struct flash_pages_layout dev_layout;
static void flash_wb_pages_layout(struct device *dev,
const struct flash_pages_layout **layout,
size_t *layout_size)
{
*layout = &dev_layout;
*layout_size = 1;
}
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
static const struct flash_parameters *
flash_wb_get_parameters(const struct device *dev)
{
ARG_UNUSED(dev);
return &flash_wb_parameters;
}
static const struct flash_driver_api spi_flash_api = {
.read = spi_flash_wb_read,
.write = spi_flash_wb_write,
.erase = spi_flash_wb_erase,
.write_protection = spi_flash_wb_write_protection_set,
.get_parameters = flash_wb_get_parameters,
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
.page_layout = flash_wb_pages_layout,
#endif
};
static int spi_flash_wb_configure(struct device *dev)
{
struct spi_flash_data *data = dev->data;
data->spi = device_get_binding(DT_INST_BUS_LABEL(0));
if (!data->spi) {
return -EINVAL;
}
data->spi_cfg.frequency = DT_INST_PROP(0, spi_max_frequency);
data->spi_cfg.operation = SPI_WORD_SET(8);
data->spi_cfg.slave = DT_INST_REG_ADDR(0);
#if DT_INST_SPI_DEV_HAS_CS_GPIOS(0)
data->cs_ctrl.gpio_dev = device_get_binding(
DT_INST_SPI_DEV_CS_GPIOS_LABEL(0));
if (!data->cs_ctrl.gpio_dev) {
return -ENODEV;
}
data->cs_ctrl.gpio_pin = DT_INST_SPI_DEV_CS_GPIOS_PIN(0);
data->cs_ctrl.gpio_dt_flags = DT_INST_SPI_DEV_CS_GPIOS_FLAGS(0);
data->cs_ctrl.delay = CONFIG_SPI_FLASH_W25QXXDV_GPIO_CS_WAIT_DELAY;
data->spi_cfg.cs = &data->cs_ctrl;
#endif
return spi_flash_wb_id(dev);
}
static int spi_flash_init(struct device *dev)
{
int ret;
SYNC_INIT();
ret = spi_flash_wb_configure(dev);
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
/*
* Note: we use the sector size rather than the page size as some
* modules that consumes the flash page layout assume the page
* size is the minimal size they can erase.
*/
dev_layout.pages_count = (CONFIG_SPI_FLASH_W25QXXDV_FLASH_SIZE / W25QXXDV_SECTOR_SIZE);
dev_layout.pages_size = W25QXXDV_SECTOR_SIZE;
#endif
return ret;
}
static struct spi_flash_data spi_flash_memory_data;
DEVICE_AND_API_INIT(spi_flash_memory, CONFIG_SPI_FLASH_W25QXXDV_DRV_NAME,
spi_flash_init, &spi_flash_memory_data, NULL, POST_KERNEL,
CONFIG_SPI_FLASH_W25QXXDV_INIT_PRIORITY, &spi_flash_api);

View file

@ -1,30 +0,0 @@
/*
* Copyright (c) 2016 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
*
* @brief This file defines the private data structures for spi flash driver
*/
#ifndef ZEPHYR_DRIVERS_FLASH_SPI_FLASH_W25QXXDV_H_
#define ZEPHYR_DRIVERS_FLASH_SPI_FLASH_W25QXXDV_H_
#define DT_DRV_COMPAT winbond_w25q16
struct spi_flash_data {
struct device *spi;
#if DT_INST_SPI_DEV_HAS_CS_GPIOS(0)
struct spi_cs_control cs_ctrl;
#endif
struct spi_config spi_cfg;
#if defined(CONFIG_MULTITHREADING)
struct k_sem sem;
#endif /* CONFIG_MULTITHREADING */
};
#endif /* ZEPHYR_DRIVERS_FLASH_SPI_FLASH_W25QXXDV_H_ */

View file

@ -1,118 +0,0 @@
/*
* Copyright (c) 2016 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
*
* @brief This file has the WinBond SPI flash private definitions
*/
#ifndef ZEPHYR_DRIVERS_FLASH_SPI_FLASH_W25QXXDV_DEFS_H_
#define ZEPHYR_DRIVERS_FLASH_SPI_FLASH_W25QXXDV_DEFS_H_
/* Status Registers
* S7 S6 S5 S4 S3 S2 S1 S0
* +-------------------------------------------------------+
* | SRP0 | SEC | TB | BP2 | BP1 | BP0 | WEL | BUSY |
* +-------------------------------------------------------+
*
* BUSY - Erase/Write In Progress - 1 device is executing a command, 0 ready for command
* WEL - Write Enable Latch - 1 write enable is received, 0 completion of
* a Write Disable, Page Program, Erase, Write Status Register
*
* S15 S14 S13 S12 S11 S10 S9 S8
* +-------------------------------------------------------+
* | SUS | CMP | LB3 | LB2 | LB1 | xxx | QE | SRP1 |
* +-------------------------------------------------------+
*
* S23 S22 S21 S20 S19 S18 S17 S16
* +----------------------------------------------------------+
* | HOLD/RST | DRV1 | DRV0 | xxx | xxx | WPS | xxx | xxx |
* +----------------------------------------------------------+
*/
#define W25QXXDV_RDID_VALUE (0x00ef4015)
#define W25QXXDV_MAX_LEN_REG_CMD (6)
#define W25QXXDV_OPCODE_LEN (1)
#define W25QXXDV_ADDRESS_WIDTH (3)
#define W25QXXDV_LEN_CMD_ADDRESS (4)
#define W25QXXDV_LEN_CMD_AND_ID (4)
/* relevant status register bits */
#define W25QXXDV_WIP_BIT (0x1 << 0)
#define W25QXXDV_WEL_BIT (0x1 << 1)
#define W25QXXDV_SRWD_BIT (0x1 << 7)
#define W25QXXDV_TB_BIT (0x1 << 3)
#define W25QXXDV_SR_BP_OFFSET (2)
/* relevant security register bits */
#define W25QXXDV_SECR_WPSEL_BIT (0x1 << 7)
#define W25QXXDV_SECR_EFAIL_BIT (0x1 << 6)
#define W25QXXDV_SECR_PFAIL_BIT (0x1 << 5)
/* supported erase size */
#define W25QXXDV_SECTOR_SIZE (0x1000)
#define W25QXXDV_BLOCK32K_SIZE (0x8000)
#define W25QXXDV_BLOCK_SIZE (0x10000)
#define W25QXXDV_SECTOR_MASK (0xFFF)
/* ID commands */
#define W25QXXDV_CMD_RDID 0x9F
#define W25QXXDV_CMD_RES 0xAB
#define W25QXXDV_CMD_REMS 0x90
#define W25QXXDV_CMD_QPIID 0xAF
#define W25QXXDV_CMD_UNID 0x4B
/*Register commands */
#define W25QXXDV_CMD_WRSR 0x01
#define W25QXXDV_CMD_RDSR 0x05
#define W25QXXDV_CMD_RDSR2 0x35
#define W25QXXDV_CMD_WRSCUR 0x2F
#define W25QXXDV_CMD_RDSCUR 0x48
/* READ commands */
#define W25QXXDV_CMD_READ 0x03
#define W25QXXDV_CMD_2READ 0xBB
#define W25QXXDV_CMD_4READ 0xEB
#define W25QXXDV_CMD_FASTREAD 0x0B
#define W25QXXDV_CMD_DREAD 0x3B
#define W25QXXDV_CMD_QREAD 0x6B
#define W25QXXDV_CMD_RDSFDP 0x5A
/* Program commands */
#define W25QXXDV_CMD_WREN 0x06
#define W25QXXDV_CMD_WRDI 0x04
#define W25QXXDV_CMD_PP 0x02
#define W25QXXDV_CMD_4PP 0x32
#define W25QXXDV_CMD_WRENVSR 0x50
/* Erase commands */
#define W25QXXDV_CMD_SE 0x20
#define W25QXXDV_CMD_BE32K 0x52
#define W25QXXDV_CMD_BE 0xD8
#define W25QXXDV_CMD_CE 0x60
/* Mode setting commands */
#define W25QXXDV_CMD_DP 0xB9
#define W25QXXDV_CMD_RDP 0xAB
/* Reset commands */
#define W25QXXDV_CMD_RSTEN 0x66
#define W25QXXDV_CMD_RST 0x99
#define W25QXXDV_CMD_RSTQIO 0xF5
/* Security commands */
#define W25QXXDV_CMD_ERSR 0x44
#define W25QXXDV_CMD_PRSR 0x42
/* Suspend/Resume commands */
#define W25QXXDV_CMD_PGM_ERS_S 0x75
#define W25QXXDV_CMD_PGM_ERS_R 0x7A
#define W25QXXDV_CMD_NOP 0x00
#endif /*ZEPHYR_DRIVERS_FLASH_SPI_FLASH_W25QXXDV_DEFS_H_*/

View file

@ -1,8 +0,0 @@
# Copyright (c) 2018, Linaro Limited
# SPDX-License-Identifier: Apache-2.0
description: SPI slave NOR FLASH
compatible: "winbond,w25q16"
include: spi-device.yaml

View file

@ -11,11 +11,7 @@
#include <stdio.h>
#include <string.h>
#if (CONFIG_SPI_FLASH_W25QXXDV - 0)
/* NB: W25Q16DV is a JEDEC spi-nor device, but has a separate driver. */
#define FLASH_DEVICE CONFIG_SPI_FLASH_W25QXXDV_DRV_NAME
#define FLASH_NAME "W25QXXDV"
#elif (CONFIG_SPI_NOR - 0) || \
#if (CONFIG_SPI_NOR - 0) || \
DT_NODE_HAS_STATUS(DT_INST(0, jedec_spi_nor), okay)
#define FLASH_DEVICE DT_LABEL(DT_INST(0, jedec_spi_nor))
#define FLASH_NAME "JEDEC SPI-NOR"

View file

@ -449,13 +449,6 @@ test_spi_lmp90100: lmp90100@2c {
#io-channel-cells = <2>;
};
test_spi_w25q16: w25q16@2d {
compatible = "winbond,w25q16";
label = "W25Q16";
reg = <0x2d>;
spi-max-frequency = <0>;
};
test_spi_ws2812_spi: ws2812-spi@2e {
compatible = "worldsemi,ws2812-spi";
label = "WS2812-SPI";