TI Davinci GPIO的分析
阿新 • • 發佈:2018-12-01
TI DaVinci GPIO分析
【整理者】61ic.com【提供者】678755
【詳細說明】GPIO分析
針對該gpio.c的硬體手冊是SPRUE25.pdf,可到TI的網站( www.ti.com)上下載,或直接在谷歌裡搜尋。
以下是兩個檔案gpio.c和gpio.h的淺析。
gpio.c
/*
* TI DaVinci GPIO Support
*
* Copyright (c) 2006 David Brownell
* Copyright (c) 2007, MontaVista Software, Inc. <
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/bitops.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/hardware/clock.h>
#include <asm/arch/irqs.h>
#include <asm/arch/hardware.h>
#include <asm/arch/gpio.h>
#include <asm/arch/cpu.h>
#include <asm/mach/irq.h>
/*
該檔案實現了gpio的各種應用功能和向核心註冊gpio的中斷例程等功能。
使用者的驅動程式可呼叫gpio_request和gpio_free使用或釋放該gpio,
可以呼叫gpio_direction_input和gpio_direction_output函式設定gpio輸入輸出方向,
呼叫gpio_get_value和gpio_set_value獲取設定值。
邏輯上各個gpio都有自己的中斷號,這樣做的目的是使得驅動程式能通過request_irq()函式來申請該gpio的中斷,而實際上除了gpio0-gpio7外,其他gpio都是共享四個gpiobank中斷號的。通過gpio_irq_handler例程實現了共享中斷號的目的。
*/
static DEFINE_SPINLOCK(gpio_lock);
/* 總共有DAVINCI_N_GPIO(71)個gpio引腳,故使用相應多的bit來記錄這些引腳的使用狀態 */
static DECLARE_BITMAP(gpio_in_use, DAVINCI_N_GPIO);
/*
申請一個gpio,其實就是檢查該gpio是否空閒,如果空閒就可以使用並將該gpio相應的bit置位
(在gpio_in_use中)。
*/
int gpio_request(unsigned gpio, const char *tag)
{
if (gpio >= DAVINCI_N_GPIO)
return -EINVAL;
if (test_and_set_bit(gpio, gpio_in_use))
return -EBUSY;
return 0;
}
EXPORT_SYMBOL(gpio_request);
/*
釋放一個gpio,其實就是清除gpio相應的控制bit位(在gpio_in_use中)。
*/
void gpio_free(unsigned gpio)
{
if (gpio >= DAVINCI_N_GPIO)
return;
clear_bit(gpio, gpio_in_use);
}
EXPORT_SYMBOL(gpio_free);
/* 獲得gpio_controller結構體指標,gpio_controller結構體是gpio的核心控制單元,裡面包含
gpio的設定和資料暫存器。該結構體和__gpio_to_controller函式在/include/asm-arm/
arch-davinci/gpio.h中定義,具體如下:
struct gpio_controller {
u32 dir;
u32 out_data;
u32 set_data;
u32 clr_data;
u32 in_data;
u32 set_rising;
u32 clr_rising;
u32 set_falling;
u32 clr_falling;
u32 intstat;
};
static inline struct gpio_controller *__iomem
__gpio_to_controller(unsigned gpio)
{
void *__iomem ptr;
if (gpio >= DAVINCI_N_GPIO)
return NULL;
if (gpio < 32)
ptr = (void *__iomem)IO_ADDRESS(DAVINCI_GPIO_BASE + 0x10);
else if (gpio < 64)
ptr = (void *__iomem)IO_ADDRESS(DAVINCI_GPIO_BASE + 0x38);
else if (gpio < 96)
ptr = (void *__iomem)IO_ADDRESS(DAVINCI_GPIO_BASE + 0x60);
else
ptr = (void *__iomem)IO_ADDRESS(DAVINCI_GPIO_BASE + 0x88);
return ptr;
}
由上面的定義和ti的SPRUE25.pdf手冊可以看出,__gpio_to_controller函式返回的是
gpio_controller結構體到第一個成員dir的虛擬地址。獲取了這個結構體指標後,
便可以控制相應的gpio了。dm644x共有71個gpio,
所以使用三個gpio_controller結構體控制,關於這個後面會由更詳細的分析,
*/
/* create a non-inlined version */
static struct gpio_controller *__iomem gpio2controller(unsigned gpio)
{
return __gpio_to_controller(gpio);
}
/*
向某個gpio設定值,0或1。如果向gpio寫1,則向set_data暫存器相應的位置1,如果寫0,
則向clr_data暫存器相應的位置1.__gpio_mask函式在gpio.h中定義,定義如下,
static inline u32 __gpio_mask(unsigned gpio)
{
return 1 << (gpio % 32);
}
因為71個引腳由3個結構體控制,第一個控制前32個gpio,第二個控制次32個gpio,
最後一個控制剩餘的7個gpio,故__gpio_mask函式的作用是找到在其相應控制結構體裡的偏移數,
比如gpio34,那麼其由第二個結構體控制,在這個機構體裡的偏移是3(從0開始算,就是第二位)。
使用這個函式之前,必須確認該gpio設定成輸出模式。
*/
/*
* Assuming the pin is muxed as a gpio output, set its output value.
*/
void __gpio_set(unsigned gpio, int value)
{
struct gpio_controller *__iomem g = gpio2controller(gpio);
// 設定gpio的值
__raw_writel(__gpio_mask(gpio), value ? &g->set_data : &g->clr_data);
}
EXPORT_SYMBOL(__gpio_set);
/*
通過讀取in_data暫存器相應該gpio的位來讀取gpio的值。
使用這個函式之前,必須確認該gpio設定成輸入模式,否則獲得到值不可預料。
*/
/*
* Read the pin's value (works even if it's set up as output);
* returns zero/nonzero.
*
* Note that changes are synched to the GPIO clock, so reading values back
* right after you've set them may give old values.
*/
int __gpio_get(unsigned gpio)
{
struct gpio_controller *__iomem g = gpio2controller(gpio);
/* 讀取gpio的值,!!的目的是使得返回的值為0或1.*/
return !!(__gpio_mask(gpio) & __raw_readl(&g->in_data));
} }
EXPORT_SYMBOL(__gpio_get);
/*
通過dir暫存器相應該gpio的位來設定gpio輸入輸出方向,為0,則設定成輸出,為1,則設定出輸入。
該函式是設定成輸入,故設定dir暫存器為1.
正如應為所說的,必須確認該引腳是作為gpio功能,而不是某個模組到功能,比如spi。通過PINMUX0
和PINMUX1兩個暫存器來設定。
*/
/*--------------------------------------------------------------------------*/
/*
* board setup code *MUST* set PINMUX0 and PINMUX1 as
* needed, and enable the GPIO clock.
*/
int gpio_direction_input(unsigned gpio)
{
struct gpio_controller *__iomem g = gpio2controller(gpio);
u32 temp;
u32 mask;
if (!g)
return -EINVAL;
spin_lock(&gpio_lock);
mask = __gpio_mask(gpio);
temp = __raw_readl(&g->dir);
temp |= mask; // 設定成1
__raw_writel(temp, &g->dir); // 設定該gpio為輸入
spin_unlock(&gpio_lock);
return 0;
}
EXPORT_SYMBOL(gpio_direction_input);
/*
通過dir暫存器相應該gpio的位來設定gpio輸入輸出方向,為0,則設定成輸出,為1,則設定出輸入。
該函式是設定成輸出,故設定dir暫存器為0.
value引數用於選擇gpio設定成輸出後該gpio輸出的值。
*/
int gpio_direction_output(unsigned gpio, int value)
{
struct gpio_controller *__iomem g = gpio2controller(gpio);
u32 temp;
u32 mask;
if (!g)
return -EINVAL;
spin_lock(&gpio_lock);
mask = __gpio_mask(gpio);
temp = __raw_readl(&g->dir);
temp &= ~mask; // 設定成0
//設定該gpio輸出值
__raw_writel(mask, value ? &g->set_data : &g->clr_data);
__raw_writel(temp, &g->dir); // 設定gpio為輸出
spin_unlock(&gpio_lock);
return 0;
}
EXPORT_SYMBOL(gpio_direction_output);
/*
向gpio設定值,0或1。
*/
void gpio_set_value(unsigned gpio, int value)
{
if (__builtin_constant_p(value)) {
struct gpio_controller *__iomem g;
u32 mask;
if (gpio >= DAVINCI_N_GPIO)
__error_inval_gpio();
g = __gpio_to_controller(gpio);
mask = __gpio_mask(gpio);
if (value)
__raw_writel(mask, &g->set_data); // 該gpio輸出高
else
__raw_writel(mask, &g->clr_data); // 該gpio輸出低
return;
}
__gpio_set(gpio, value);
}
EXPORT_SYMBOL(gpio_set_value);
/*
讀取gpio的值,0或1.
*/
int gpio_get_value(unsigned gpio)
{
struct gpio_controller *__iomem g;
if (!__builtin_constant_p(gpio))/* 判斷該gpio值是否為編譯時常數,如果是常數,
函式返回 1,否則返回 0 */
return __gpio_get(gpio);
if (gpio >= DAVINCI_N_GPIO)
return __error_inval_gpio();
g = __gpio_to_controller(gpio);
// 讀取該gpio的值
return !!(__gpio_mask(gpio) & __raw_readl(&g->in_data));
}
EXPORT_SYMBOL(gpio_get_value);
/*
* We expect irqs will normally be set up as input pins, but they can also be
* used as output pins ... which is convenient for testing.
*
* NOTE: GPIO0..GPIO7 also have direct INTC hookups, which work in addition
* to their GPIOBNK0 irq (but with a bit less overhead). But we don't have
* a good way to hook those up ...
*
* All those INTC hookups (GPIO0..GPIO7 plus five IRQ banks) can also
* serve as EDMA event triggers.
*/
/*
禁止相應該irq的gpio的中斷。每個gpio都可以作為中斷的來源,其中gpio0-gpio7是獨立的中斷來源,
也就是分配獨立的中斷號,其他gpio則共用5個GPIOBNK中斷線。其優先順序可以在board-evm.c
中設定(已經介紹過)。在dm644x平臺上,中斷是電平邊緣觸發的,禁止中斷其實就是既不設定
上升沿觸發,也不設定下降沿觸發。
*/
static void gpio_irq_disable(unsigned irq)
{
struct gpio_controller *__iomem g = get_irq_chipdata(irq);
u32 mask = __gpio_mask(irq_to_gpio(irq));
__raw_writel(mask, &g->clr_falling); // 清除下降沿觸發
__raw_writel(mask, &g->clr_rising); // 清除上升沿觸發
}
/*
中斷使能。
在dm644x平臺上,中斷是電平邊緣觸發的,其實就是設定為上升沿或下降沿中斷。
*/
static void gpio_irq_enable(unsigned irq)
{
struct gpio_controller *__iomem g = get_irq_chipdata(irq);
u32 mask = __gpio_mask(irq_to_gpio(irq));
// 如果先前為下降沿中斷,則使能為下降沿中斷
if (irq_desc[irq].status & IRQT_FALLING)
__raw_writel(mask, &g->set_falling);
// 如果先前為上升沿中斷,則使能為上升沿中斷
if (irq_desc[irq].status & IRQT_RISING)
__raw_writel(mask, &g->set_rising);
}
/*
設定中斷型別。
在dm644x平臺上,中斷有上升沿和下降沿兩種觸發方式。
*/
static int gpio_irq_type(unsigned irq, unsigned trigger)
{
struct gpio_controller *__iomem g = get_irq_chipdata(irq);
u32 mask = __gpio_mask(irq_to_gpio(irq));
if (trigger & ~(IRQT_FALLING | IRQT_RISING))
return -EINVAL;
irq_desc[irq].status &= ~IRQT_BOTHEDGE;
irq_desc[irq].status |= trigger;
__raw_writel(mask, (trigger & IRQT_FALLING)
? &g->set_falling : &g->clr_falling); // 設定為下降沿觸發
__raw_writel(mask, (trigger & IRQT_RISING)
? &g->set_rising : &g->clr_rising); // 設定為上升沿觸發
return 0;
}
/*
該結構體用於註冊到所有irq的中斷描述結構體中(struct irqdesc),
而所有中斷描述結構體定義成一個全域性陣列irq_desc 。
*/
static struct irqchip gpio_irqchip = {
.unmask = gpio_irq_enable, /* 用於使能中斷,
在enable_irq()等核心函式中會用到。*/
.mask = gpio_irq_disable,/* 用於禁止中斷,
在disable_irq()等核心函式中會用到。*/
.type = gpio_irq_type, /* 用於設定中斷型別,
在set_irq_type()核心函式中會用到。*/
};
/*
該函式將在下面的davinci_gpio_irq_setup中使用,將被註冊到五個gpio bank中斷的
irq_desc結構中,目的是處理所有級聯的gpio中斷。所謂級聯的中斷, 就是指有n箇中斷
共用同一個中斷線。
在dm644x平臺中,除了gpio0-gpio7外,其他63個gpio都共用五個gpiobank中斷線,在這裡,
gpio0-gpio7也被註冊到gpiobank中斷線,但實際上他們會在irq.c中重新註冊。其中,gpio0-gpio15共用IRQ_GPIOBNK0(56)中斷線,gpio16-gpio31共用
IRQ_GPIOBNK1(57)中斷線,gpio32-gpio47共用IRQ_GPIOBNK2(58)中斷線,
gpio48-gpio63共用IRQ_GPIOBNK4(59)中斷線,gpio64-gpio70共用
IRQ_GPIOBNK5(60)中斷線,
因為暫存器是32位的,所以實際上只有三組暫存器,第一組包含bank0和bank1,
也就是gpio0-gpio31,第二組包含bank2和bank3,也就是gpio32-gpio63,
第三組包含bank4和bank5,也就是gpio64-gpio70,剩餘了25個位沒有使用。
*/
static void
gpio_irq_handler(unsigned irq, struct irqdesc *desc, struct pt_regs *regs)
{
struct gpio_controller *__iomem g = get_irq_chipdata(irq);
u32 mask = 0xffff;
/* we only care about one bank */
// 如果bank中斷線是寄數,則說明該中斷的中斷狀態位在INTSTATn暫存器的高16位
if (irq & 1)
mask <<= 16;
/* temporarily mask (level sensitive) parent IRQ */
desc->chip->ack(irq);// 該ack函式會在arch/arm/mach-davinci/irq.c中註冊。
while (1) {
u32 status;
struct irqdesc *gpio;
int n;
int res;
/* ack any irqs */
/*gpio中斷髮生後,硬體會在INTSTATn暫存器中置位相應位,
以備程式查詢,確定是哪個gpio*/
status = __raw_readl(&g->intstat) & mask;
if (!status)
break;
__raw_writel(status, &g->intstat); // 向該位寫1清除
if (irq & 1)
status >>= 16;
/* now demux them to the right lowlevel handler */
// 從下面的davinci_gpio_irq_setup函式可以看出來以下程式的運作。
n = (int)get_irq_data(irq); // 獲取該bank對應的第一個gpio號
gpio = &irq_desc[n]; // 獲取該bank第一個gpio號對應的中斷描述符
while (status) { // 該bank可能有多個gpio發生了中斷
res = ffs(status); // 獲取第一個發生了中斷的位(1-32)
n += res; /* 獲得該gpio的中斷線(系統實際上只有64(0-63)箇中斷線,
但那些共用的gpio的中斷也有自己的斷描述符和中斷線(從64開始),
這樣做的目的是使得驅動程式能通過request_irq()函式來申請該gpio的中斷。*/
gpio += res; // 獲得該gpio的中斷描述符
/* 呼叫下面註冊的do_simple_IRQ例程
其又會呼叫使用者通過request_irq()
註冊的中斷例程
*/
desc_handle_irq(n - 1, gpio - 1, regs);
status >>= res;
}
}
desc->chip->unmask(irq); // 開啟該irq中斷線
/* now it may re-trigger */
}
/*
* NOTE: for suspend/resume, probably best to make a sysdev (and class)
* with its suspend/resume calls hooking into the results of the set_wake()
* calls ... so if no gpios are wakeup events the clock can be disabled,
* with outputs left at previously set levels, and so that VDD3P3V.IOPWDN0
* can be set appropriately for GPIOV33 pins.
*/
/*
註冊gpio中斷例程到核心中,並初始化了一些暫存器。
該函式將會被board_evm.c(其淺析已經發表)中的evm_init()函式呼叫。具體呼叫過程如下:
start_kernel()-->setup_arch()-->init_machine = mdesc->init_machine
(init_machine是個全域性函式指標變數,其指向的就是已經註冊到機器描述符裡evm_init());
呼叫函式指標init_machine()的例程是customize_machine(),其定義為
arch_initcall(customize_machine),所以,接下來的呼叫過程是:
start_kernel()-->do_basic_setup()-->do_initcalls()-->customize_machine()-->
init_machine()(也就是evm_init())-->davinci_gpio_irq_setup。
從上可以看出經歷了兩個過程,才呼叫davinci_gpio_irq_setup例程來初始化gpio中斷。
*/
int __init davinci_gpio_irq_setup(void)
{
unsigned gpio, irq, bank, banks;
struct clk *clk;
clk = clk_get(NULL, "gpio"); // 獲取時鐘
if (IS_ERR(clk)) {
printk(KERN_ERR "Error %ld getting gpio clock?\n",
PTR_ERR(clk));
return 0;
}
clk_enable(clk); // 使能gpio時鐘並開啟該模組電源
for (gpio = 0, irq = gpio_to_irq(0), bank = (cpu_is_davinci_dm355() ?
IRQ_DM355_GPIOBNK0 : (cpu_is_davinci_dm6467() ?
IRQ_DM646X_GPIOBNK0 : IRQ_GPIOBNK0)); // dm644x的IRQ_GPIOBNK0(56)
gpio < DAVINCI_N_GPIO; bank++) { // dm644x的DAVINCI_N_GPIO(71)
struct gpio_controller *__iomem g = gpio2controller(gpio);
unsigned i;
// 關該bank所有gpio的中斷
__raw_writel(~0, &g->clr_falling);
__raw_writel(~0, &g->clr_rising);
/* set up all irqs in this bank */
// 同一個bank的所有gpio共用一箇中斷例程gpio_irq_handler
set_irq_chained_handler(bank, gpio_irq_handler);
set_irq_chipdata(bank, g);
set_irq_data(bank, (void *)irq);
for (i = 0; i < 16 && gpio < DAVINCI_N_GPIO;
i++, irq++, gpio++) {
set_irq_chip(irq, &gpio_irqchip); /* 註冊用於gpio中斷禁止、設能
和型別選擇的回撥例程 */
set_irq_chipdata(irq, g); // 儲存控制結構體(暫存器)的地址
set_irq_handler(irq, do_simple_IRQ);/* 為每個gpio中斷設定同一個中
斷例程do_simple_IRQ*/
set_irq_flags(irq, IRQF_VALID); // fiq中斷有效
}
}
/*
一個共用bank中斷線的gpio中斷髮生後的大致的流程是:
--> gpio_irq_handler --> do_simple_IRQ --> __do_irq -->
action->handler(使用者使用request_irq()註冊的中斷例程)
*/
/* BINTEN -- per-bank interrupt enable. genirq would also let these
* bits be set/cleared dynamically.
*/
if (cpu_is_davinci_dm355())
banks = 0x3f;
else
banks = 0x1f;
// 向BINTEN暫存器寫入0x1f(共5個位,每個位控制1個bank),開啟所有的bank中斷
__raw_writel(banks, (void *__iomem)
IO_ADDRESS(DAVINCI_GPIO_BASE + 0x08));
printk(KERN_INFO "DaVinci: %d gpio irqs\n", irq - gpio_to_irq(0));
return 0;
}
gpio.h
/*
* TI DaVinci GPIO Support
*
* Copyright (c) 2006 David Brownell
* Copyright (c) 2007, MontaVista Software, Inc. <
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef __DAVINCI_GPIO_H
#define __DAVINCI_GPIO_H
/*
* basic gpio routines
*
* board-specific init should be done by arch/.../.../board-XXX.c (maybe
* initializing banks together) rather than boot loaders; kexec() won't
* go through boot loaders.
*
* the gpio clock will be turned on when gpios are used, and you may also
* need to pay attention to PINMUX0 and PINMUX1 to be sure those pins are
* used as gpios, not with other peripherals.
*
* GPIOs are numbered 0..(DAVINCI_N_GPIO-1). For documentation, and maybe
* for later updates, code should write GPIO(N) or:
* - GPIOV18(N) for 1.8V pins, N in 0..53; same as GPIO(0)..GPIO(53)
* - GPIOV33(N) for 3.3V pins, N in 0..17; same as GPIO(54)..GPIO(70)
*
* For GPIO IRQs use gpio_to_irq(GPIO(N)) or gpio_to_irq(GPIOV33(N)) etc
* for now, that's != GPIO(N)
*/
#define GPIO(X) (X) /* 0 <= X <= 70 */
#define GPIOV18(X) (X) /* 1.8V i/o; 0 <= X <= 53 */
#define GPIOV33(X) ((X)+54) /* 3.3V i/o; 0 <= X <= 17 */
/*
暫存器都是32位到,每位對應一個gpio。
*/
struct gpio_controller {
u32 dir; // gpio方向設定暫存器
u32 out_data; // gpio設定為輸出時,表示輸出狀態(0或1)
u32 set_data; // gpio設定為輸出時,用於輸出高電平
u32 clr_data; // gpio設定為輸出時,用於輸出低電平
u32 in_data; // gpio設定為輸入時,用於讀取輸入值
u32 set_rising; // gpio中斷上升沿觸發設定
u32 clr_rising; // gpio中斷上升沿觸發清除
u32 set_falling; // gpio中斷下降沿觸發設定
u32 clr_falling; // gpio中斷下降沿觸發清除
u32 intstat; // gpio中斷狀態位,由硬體設定,可讀取,寫1時清除。
};
/* The __gpio_to_controller() and __gpio_mask() functions inline to constants
* with constant parameters; or in outlined code they execute at runtime.
*
* You'd access the controller directly when reading or writing more than
* one gpio value at a time, and to support wired logic where the value
* being driven by the cpu need not match the value read back.
*
* These are NOT part of the cross-platform GPIO interface
*/
static inline struct gpio_controller *__iomem
__gpio_to_controller(unsigned gpio)
{
void *__iomem ptr;
if (gpio >= DAVINCI_N_GPIO)
return NULL;
if (gpio < 32)
ptr = (void *__iomem)IO_ADDRESS(DAVINCI_GPIO_BASE + 0x10);
else if (gpio < 64)
ptr = (void *__iomem)IO_ADDRESS(DAVINCI_GPIO_BASE + 0x38);
else if (gpio < 96)
ptr = (void *__iomem)IO_ADDRESS(DAVINCI_GPIO_BASE + 0x60);
else
ptr = (void *__iomem)IO_ADDRESS(DAVINCI_GPIO_BASE + 0x88);
return ptr;
}
static inline u32 __gpio_mask(unsigned gpio)
{
return 1 << (gpio % 32);
}
/* The get/set/clear functions will inline when called with constant
* parameters, for low-overhead bitbanging. Illegal constant parameters
* cause link-time errors.
*
* Otherwise, calls with variable parameters use outlined functions.
*/
extern int __error_inval_gpio(void);
extern void __gpio_set(unsigned gpio, int value);
extern int __gpio_get(unsigned gpio);
/* Returns zero or nonzero; works for gpios configured as inputs OR
* as outputs.
*
* NOTE: changes in reported values are synchronized to the GPIO clock.
* This is most easily seen after calling gpio_set_value() and then immediatly
* gpio_get_value(), where the gpio_get_value() would return the old value
* until the GPIO clock ticks and the new value gets latched.
*/
extern int gpio_get_value(unsigned gpio);
extern void gpio_set_value(unsigned gpio, int value);
/* powerup default direction is IN */
extern int gpio_direction_input(unsigned gpio);
extern int gpio_direction_output(unsigned gpio, int value);
#include <asm-generic/gpio.h> /* cansleep wrappers */
extern int gpio_request(unsigned gpio, const char *tag);
extern void gpio_free(unsigned gpio);
static inline int gpio_to_irq(unsigned gpio)
{
return DAVINCI_N_AINTC_IRQ + gpio;
}
static inline int irq_to_gpio(unsigned irq)
{
return irq - DAVINCI_N_AINTC_IRQ;
}
#endif /* __DAVINCI_GPIO_H */
<script> (function(){ function setArticleH(btnReadmore,posi){ var winH = $(window).height(); var articleBox = $("div.article_content"); var artH = articleBox.height(); if(artH > winH*posi){ articleBox.css({ 'height':winH*posi+'px', 'overflow':'hidden' }) btnReadmore.click(function(){ if(!window.csdn.anonymousUserLimit.judgment()){ window.csdn.anonymousUserLimit.Jumplogin(); return false; }else if(!currentUserName){ window.csdn.anonymousUserLimit.updata(); } articleBox.removeAttr("style"); $(this).parent().remove(); }) }else{ btnReadmore.parent().remove(); } } var btnReadmore = $("#btn-readmore"); if(btnReadmore.length>0){ if(currentUserName){ setArticleH(btnReadmore,3); }else{ setArticleH(btnReadmore,1.2); } } })() </script> </article>