/* 
 | 
 * (C) Copyright 2021 Rockchip Electronics Co., Ltd 
 | 
 * 
 | 
 * SPDX-License-Identifier:     GPL-2.0+ 
 | 
 */ 
 | 
#include <common.h> 
 | 
#include <adc.h> 
 | 
#include <asm/io.h> 
 | 
#include <dm/ofnode.h> 
 | 
#include <asm/arch/resource_img.h> 
 | 
  
 | 
#define is_digit(c)        ((c) >= '0' && (c) <= '9') 
 | 
#define is_abcd(c)        ((c) >= 'a' && (c) <= 'd') 
 | 
#define is_equal(c)        ((c) == '=') 
 | 
#define KEY_WORDS_ADC_CTRL    "#_" 
 | 
#define KEY_WORDS_ADC_CH    "_ch" 
 | 
#define KEY_WORDS_GPIO        "#gpio" 
 | 
#define MAX_ADC_CH_NR        10 
 | 
#define MAX_GPIO_NR        10 
 | 
  
 | 
static fdt_addr_t gpio_base_addr[MAX_GPIO_NR]; 
 | 
static uint32_t gpio_record[MAX_GPIO_NR]; 
 | 
static int adc_record[MAX_ADC_CH_NR]; 
 | 
  
 | 
#ifdef CONFIG_ROCKCHIP_GPIO_V2 
 | 
#define GPIO_SWPORT_DDR        0x08 
 | 
#define GPIO_EXT_PORT        0x70 
 | 
#define WMSK_SETBIT(n)        (n << 16 | n) 
 | 
#define WMSK_CLRBIT(n)        (n << 16) 
 | 
#define REG_PLUS4(off, n)    (off + (n >= BIT(16) ? 4 : 0)) 
 | 
#define BIT_SUB16(n)        (n >= BIT(16) ? (n >> 16) : n) 
 | 
static int gpio_read(fdt_addr_t gpio_addr, int gpio_bank, int gpio_pin) 
 | 
{ 
 | 
    uint32_t off, bit; 
 | 
  
 | 
    bit = gpio_bank * 8 + gpio_pin; 
 | 
    off = REG_PLUS4(GPIO_SWPORT_DDR, bit); 
 | 
    bit = BIT_SUB16(bit); 
 | 
    writel(WMSK_CLRBIT(bit), gpio_addr + off); 
 | 
  
 | 
    return readl(gpio_addr + GPIO_EXT_PORT); 
 | 
} 
 | 
  
 | 
#else 
 | 
#define GPIO_SWPORT_DDR        0x04 
 | 
#define GPIO_EXT_PORT        0x50 
 | 
static int gpio_read(fdt_addr_t gpio_addr, int gpio_bank, int gpio_pin) 
 | 
{ 
 | 
    uint32_t val; 
 | 
  
 | 
    val = readl(gpio_addr + GPIO_SWPORT_DDR); 
 | 
    val &= ~(1 << (gpio_bank * 8 + gpio_pin)); 
 | 
    writel(val, gpio_addr + GPIO_SWPORT_DDR); 
 | 
  
 | 
    return readl(gpio_addr + GPIO_EXT_PORT); 
 | 
} 
 | 
#endif 
 | 
  
 | 
static int gpio_parse_base_address(fdt_addr_t *gpio_base_addr) 
 | 
{ 
 | 
    static int initialized; 
 | 
    ofnode parent, node; 
 | 
    int idx = 0; 
 | 
  
 | 
    if (initialized) 
 | 
        return 0; 
 | 
  
 | 
    parent = ofnode_path("/pinctrl"); 
 | 
    if (!ofnode_valid(parent)) { 
 | 
        debug("   - No pinctrl node\n"); 
 | 
        return -EINVAL; 
 | 
    } 
 | 
  
 | 
    ofnode_for_each_subnode(node, parent) { 
 | 
        if (!ofnode_get_property(node, "gpio-controller", NULL)) { 
 | 
            debug("   - No gpio controller node\n"); 
 | 
            continue; 
 | 
        } 
 | 
        gpio_base_addr[idx] = ofnode_get_addr(node); 
 | 
        debug("   - gpio%d: 0x%x\n", idx, (uint32_t)gpio_base_addr[idx]); 
 | 
        idx++; 
 | 
    } 
 | 
  
 | 
    if (idx == 0) { 
 | 
        debug("   - parse gpio address failed\n"); 
 | 
        return -EINVAL; 
 | 
    } 
 | 
  
 | 
    initialized = 1; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static void hwid_init_data(void) 
 | 
{ 
 | 
    memset(adc_record, 0, sizeof(adc_record)); 
 | 
    memset(gpio_record, 0, sizeof(gpio_record)); 
 | 
    memset(gpio_base_addr, 0, sizeof(gpio_base_addr)); 
 | 
} 
 | 
  
 | 
/* 
 | 
 * How to use ? 
 | 
 * 
 | 
 * 1. Pack dtbs into resource.img, dtb file name: 
 | 
 *    - End with: ".dtb" 
 | 
 *    - Pattern: ...#_[controller]_ch[channel]=[value]...dtb 
 | 
 *        @controller: adc controller name in dts, eg. "saradc", ...; 
 | 
 *        @channel: adc channel; 
 | 
 *        @value: adc value; 
 | 
 *    - Eg: ...#_saradc_ch1=223#_saradc_ch2=650....dtb 
 | 
 * 
 | 
 * 2. U-Boot dtsi about adc node: 
 | 
 *    - Add "u-boot,dm-pre-reloc;" 
 | 
 *    - Set node "okay" 
 | 
 */ 
 | 
static int hwid_adc_find_dtb(const char *file_name) 
 | 
{ 
 | 
    char *cell_name, *adc_tail, *adc_head, *p; 
 | 
    int prefix_len, chn_len, len; 
 | 
    int found = 0, margin = 30; 
 | 
    int ret, channel; 
 | 
    char dev_name[32]; 
 | 
    char adc_val[10]; 
 | 
    ulong dtb_adc; 
 | 
    u32 raw_adc; 
 | 
  
 | 
    debug("[HW-ADC]: %s\n", file_name); 
 | 
  
 | 
    chn_len = strlen(KEY_WORDS_ADC_CH); 
 | 
    prefix_len = strlen(KEY_WORDS_ADC_CTRL); 
 | 
    cell_name = strstr(file_name, KEY_WORDS_ADC_CTRL); 
 | 
    while (cell_name) { 
 | 
        /* Parse adc controller name */ 
 | 
        adc_tail = strstr(cell_name, KEY_WORDS_ADC_CH); 
 | 
        adc_head = cell_name + prefix_len; 
 | 
        len = adc_tail - adc_head; 
 | 
        strlcpy(dev_name, adc_head, len + 1); 
 | 
  
 | 
        /* Parse adc channel */ 
 | 
        p = adc_tail + chn_len; 
 | 
        if (is_digit(*p) && is_equal(*(p + 1))) { 
 | 
            channel = *p - '0'; 
 | 
        } else { 
 | 
            debug("   - invalid format: %s\n", cell_name); 
 | 
            return 0; 
 | 
        } 
 | 
  
 | 
        /* 
 | 
         * Read raw adc value 
 | 
         * 
 | 
         * It doesn't need to read adc value every loop, reading once 
 | 
         * is enough. We use adc_record[] to save what we have read, zero 
 | 
         * means not read before. 
 | 
         */ 
 | 
        if (adc_record[channel] == 0) { 
 | 
            ret = adc_channel_single_shot(dev_name, channel, &raw_adc); 
 | 
            if (ret) { 
 | 
                debug("   - failed to read adc, ret=%d\n", ret); 
 | 
                return 0; 
 | 
            } 
 | 
            adc_record[channel] = raw_adc; 
 | 
        } 
 | 
  
 | 
        /* Parse dtb adc value */ 
 | 
        p = adc_tail + chn_len + 2;    /* 2: channel and '=' */ 
 | 
        while (*p && is_digit(*p)) { 
 | 
            len++; 
 | 
            p++; 
 | 
        } 
 | 
        strlcpy(adc_val, adc_tail + chn_len + 2, len + 1); 
 | 
        dtb_adc = simple_strtoul(adc_val, NULL, 10); 
 | 
        found = (abs(dtb_adc - adc_record[channel]) <= margin) ? 1 : 0; 
 | 
        debug("   - dev=%s, channel=%d, dtb_adc=%ld, read=%d, found=%d\n", 
 | 
              dev_name, channel, dtb_adc, adc_record[channel], found); 
 | 
        if (!found) 
 | 
            break; 
 | 
        cell_name = strstr(p, KEY_WORDS_ADC_CTRL); 
 | 
  
 | 
    } 
 | 
  
 | 
    return found; 
 | 
} 
 | 
  
 | 
/* 
 | 
 * How to use ? 
 | 
 * 
 | 
 * 1. Pack dtbs into resource.img, dtb file name: 
 | 
 *    - End with: ".dtb" 
 | 
 *    - Pattern: ...#gpio[pin]=[level]...dtb 
 | 
 *         @pin: gpio name, eg. 0a2 means GPIO0A2 
 | 
 *         @level: gpio level, 0 or 1 
 | 
 *    - Eg: ...#gpio0a6=1#gpio1c2=0....dtb 
 | 
 * 
 | 
 * 2. U-Boot dtsi about gpio node: 
 | 
 *    - Add "u-boot,dm-pre-reloc;" for [all] gpio node; 
 | 
 *    - Set all gpio node "disabled" (We just want their property); 
 | 
 */ 
 | 
static int hwid_gpio_find_dtb(const char *file_name) 
 | 
{ 
 | 
    uint8_t port, pin, bank, lvl, val; 
 | 
    char *cell_name, *p; 
 | 
    int ret, prefix_len; 
 | 
    int found = 0; 
 | 
    u32 bit; 
 | 
  
 | 
    debug("[HW-GPIO]: %s\n", file_name); 
 | 
  
 | 
    if (gpio_base_addr[0] == 0) { 
 | 
        ret = gpio_parse_base_address(gpio_base_addr); 
 | 
        if (ret) { 
 | 
            debug("[HW-GPIO]: Can't parse gpio base, ret=%d\n", ret); 
 | 
            return 0; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    prefix_len = strlen(KEY_WORDS_GPIO); 
 | 
    cell_name = strstr(file_name, KEY_WORDS_GPIO); 
 | 
    while (cell_name) { 
 | 
        p = cell_name + prefix_len; 
 | 
  
 | 
        /* Invalid format ? */ 
 | 
        if (!(is_digit(*(p + 0)) && is_abcd(*(p + 1)) && 
 | 
              is_digit(*(p + 2)) && is_equal(*(p + 3)) && 
 | 
              is_digit(*(p + 4)))) { 
 | 
            debug("   - invalid format: %s\n", cell_name); 
 | 
            return 0; 
 | 
        } 
 | 
  
 | 
        /* Read gpio value */ 
 | 
        port = *(p + 0) - '0'; 
 | 
        bank = *(p + 1) - 'a'; 
 | 
        pin  = *(p + 2) - '0'; 
 | 
        lvl  = *(p + 4) - '0'; 
 | 
  
 | 
        /* 
 | 
         * It doesn't need to read gpio value every loop, reading once 
 | 
         * is enough. We use gpio_record[] to save what we have read, zero 
 | 
         * means not read before. 
 | 
         */ 
 | 
        if (gpio_record[port] == 0) { 
 | 
            if (!gpio_base_addr[port]) { 
 | 
                debug("   - can't find gpio%d base\n", port); 
 | 
                return 0; 
 | 
            } 
 | 
            gpio_record[port] = 
 | 
                gpio_read(gpio_base_addr[port], bank, pin); 
 | 
        } 
 | 
  
 | 
        /* Verify result */ 
 | 
        bit = bank * 8 + pin; 
 | 
        val = gpio_record[port] & (1 << bit) ? 1 : 0; 
 | 
        found = (val == !!lvl) ? 1 : 0; 
 | 
        debug("   - gpio%d%c%d=%d, read=%d, found=%d\n", 
 | 
              port, bank + 'a', pin, lvl, val, found); 
 | 
        if (!found) 
 | 
            break; 
 | 
        cell_name = strstr(p, KEY_WORDS_GPIO); 
 | 
    } 
 | 
  
 | 
    return found; 
 | 
} 
 | 
  
 | 
struct resource_file *resource_read_hwid_dtb(void) 
 | 
{ 
 | 
    struct resource_file *file; 
 | 
    struct list_head *node; 
 | 
  
 | 
    hwid_init_data(); 
 | 
  
 | 
    if (list_empty(&entrys_head)) { 
 | 
        if (resource_init_list()) 
 | 
            return NULL; 
 | 
    } 
 | 
  
 | 
    list_for_each(node, &entrys_dtbs_head) { 
 | 
        file = list_entry(node, struct resource_file, dtbs); 
 | 
        if (!strstr(file->name, DTB_SUFFIX)) 
 | 
            continue; 
 | 
  
 | 
        if (strstr(file->name, KEY_WORDS_ADC_CTRL) && 
 | 
            strstr(file->name, KEY_WORDS_ADC_CH) && 
 | 
            hwid_adc_find_dtb(file->name)) { 
 | 
            return file; 
 | 
        } else if (strstr(file->name, KEY_WORDS_GPIO) && 
 | 
               hwid_gpio_find_dtb(file->name)) { 
 | 
            return file; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    return NULL; 
 | 
} 
 |