第一句子网 - 唯美句子、句子迷、好句子大全
第一句子网 > Linux下的Backlight子系统

Linux下的Backlight子系统

时间:2021-05-31 15:02:48

相关推荐

Linux下的Backlight子系统

版权所有,转载必须说明转自/weiqing1981127

原创作者:南京邮电大学 通信与信息系统专业 研二 魏清

一.Backlight背光子系统概述

我们的LCD屏常常需要一个背光,调节LCD屏背光的亮度,这里所说的背光不是仅仅亮和不亮两种,而是根据用户的需求,背光亮度是可以任意调节。Linux内核中有一个backlight背光子系统,该系统就是为满足用户这种需求设计的,用户只要根据自己的LCD背光电路中PWM输出引脚,对内核backlight子系统代码进行相应的配置,就可以实现LCD的背光。

LCD的背光原理主要是由核心板的一根引脚控制背光电源,一根PWM引脚控制背光亮度组成,应用程序可以通过改变PWM的频率达到改变背光亮度的目的。

我们这里主要讲解基于backlight子系统的蜂鸣器驱动,其实简单的使得蜂蜜器发声的驱动很简单,这里只是把蜂鸣器作为一种设备,而且这种设备原理类似背光的原理,都是基于pwm的,而我们的终极目的是使用backlight背光子系统。综上所述,backlight子系统是基于pwm核心的一种驱动接口,如果你使用的一种设备也是基于pwm的,并且需要用户可以调节pwm的频率以达到诸如改变背光亮度,改变蜂鸣器频率的效果,那么你可以使用这个backlight背光子系统。

二.PWM核心驱动

我们先讲解下PWM核心

先熟悉下pwm核心代码在/arch/arm/plat-s3c/pwm.c

查看/arch/arm/plat-s3c/Makefile

obj-$(CONFIG_HAVE_PWM) += pwm.o

查看/arch/arm/plat-s3c/Konfig,发现同目录的Konfig中无对应HAVE_PWM选项

查看/arch/arm/plat-s3c24xx/Konfig

config S3C24XX_PWM

bool "PWM device support"

select HAVE_PWM

help

Support for exporting the PWM timer blocks via the pwm device

system.

所以配置内核make menuconfig时,需要选中这一项。

好了,我们看看pwm.c,它是pwm核心驱动,该驱动把设备和驱动没有分离开来,都写在了这个pwm.c中,我们先看看pwm.c中的驱动部分

static int __init pwm_init(void)

{

int ret;

clk_scaler[0] = clk_get(NULL, "pwm-scaler0"); //获取0号时钟

clk_scaler[1] = clk_get(NULL, "pwm-scaler1"); //获取1号时钟

if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) {

printk(KERN_ERR "%s: failed to get scaler clocks\n", __func__);

return -EINVAL;

}

ret = platform_driver_register(&s3c_pwm_driver); //注册pwm驱动

if (ret)

printk(KERN_ERR "%s: failed to add pwm driver\n", __func__);

return ret;

}

跟踪下s3c_pwm_driver的定义

static struct platform_driver s3c_pwm_driver = {

.driver = {

.name = "s3c24xx-pwm", //驱动名

.owner = THIS_MODULE,

},

.probe = s3c_pwm_probe, //探测函数

.remove = __devexit_p(s3c_pwm_remove),

};

我们看看探测函数s3c_pwm_probe

static int s3c_pwm_probe(struct platform_device *pdev)

{

struct device *dev = &pdev->dev;

struct pwm_device *pwm;

unsigned long flags;

unsigned long tcon;

unsigned int id = pdev->id;

int ret;

if (id == 4) {

dev_err(dev, "TIMER4 is currently not supported\n");

return -ENXIO;

}

pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); //分配pwm设备空间

if (pwm == NULL) {

dev_err(dev, "failed to allocate pwm_device\n");

return -ENOMEM;

}

pwm->pdev = pdev;

pwm->pwm_id = id;

pwm->tcon_base = id == 0 ? 0 : (id * 4) + 4; //计算TCON中控制哪个定时器

pwm->clk = clk_get(dev, "pwm-tin"); //获取预分频后的时钟

if (IS_ERR(pwm->clk)) {

dev_err(dev, "failed to get pwm tin clk\n");

ret = PTR_ERR(pwm->clk);

goto err_alloc;

}

pwm->clk_div = clk_get(dev, "pwm-tdiv");

if (IS_ERR(pwm->clk_div)) { //获取二次分频后的时钟

dev_err(dev, "failed to get pwm tdiv clk\n");

ret = PTR_ERR(pwm->clk_div);

goto err_clk_tin;

}

local_irq_save(flags);

tcon = __raw_readl(S3C2410_TCON);

tcon |= pwm_tcon_invert(pwm); //信号反转输出

__raw_writel(tcon, S3C2410_TCON);

local_irq_restore(flags);

ret = pwm_register(pwm); //注册pwm设备

if (ret) {

dev_err(dev, "failed to register pwm\n");

goto err_clk_tdiv;

}

pwm_dbg(pwm, "config bits %02x\n",

(__raw_readl(S3C2410_TCON) >> pwm->tcon_base) & 0x0f);

dev_info(dev, "tin at %lu, tdiv at %lu, tin=%sclk, base %d\n",

clk_get_rate(pwm->clk),

clk_get_rate(pwm->clk_div),

pwm_is_tdiv(pwm) ? "div" : "ext", pwm->tcon_base);

platform_set_drvdata(pdev, pwm);

return 0;

err_clk_tdiv:

clk_put(pwm->clk_div);

err_clk_tin:

clk_put(pwm->clk);

err_alloc:

kfree(pwm);

return ret;

}

下面看看注册pwm设备的函数pwm_register

static LIST_HEAD(pwm_list);

static int pwm_register(struct pwm_device *pwm)

{

pwm->duty_ns = -1;

pwm->period_ns = -1;

mutex_lock(&pwm_lock);

list_add_tail(&pwm->list, &pwm_list); //把pwm设备挂到pwm_list链表上

mutex_unlock(&pwm_lock);

return 0;

}

剩下来,我们看看这个pwm.c给我们提供了哪些接口函数

struct pwm_device *pwm_request(int pwm_id, const char *label)

int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)

int pwm_enable(struct pwm_device *pwm)

void pwm_free(struct pwm_device *pwm)

EXPORT_SYMBOL(pwm_request); //申请PWM设备

EXPORT_SYMBOL(pwm_config); //配置PWM设备,duty_ns为空占比,period_ns为周期

EXPORT_SYMBOL(pwm_enable); //启动Timer定时器

EXPORT_SYMBOL(pwm_disable); //关闭Timer定时器

上面这个函数,只要知道API,会调用就行了,在此,我分析下最难的一个配置PWM函数,这个函数主要是根据周期period_ns,计算TCNT,根据空占比duty_ns,计算TCMP,然后写入相应寄存器。

int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)

{

unsigned long tin_rate;

unsigned long tin_ns;

unsigned long period;

unsigned long flags;

unsigned long tcon;

unsigned long tcnt;

long tcmp;

if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ)

return -ERANGE;

if (duty_ns > period_ns)

return -EINVAL;

if (period_ns == pwm->period_ns &&

duty_ns == pwm->duty_ns)

return 0;

tcmp = __raw_readl(S3C2410_TCMPB(pwm->pwm_id));

tcnt = __raw_readl(S3C2410_TCNTB(pwm->pwm_id));

period = NS_IN_HZ / period_ns; //计算周期

pwm_dbg(pwm, "duty_ns=%d, period_ns=%d (%lu)\n",

duty_ns, period_ns, period);

if (pwm->period_ns != period_ns) {

if (pwm_is_tdiv(pwm)) {

tin_rate = pwm_calc_tin(pwm, period);

clk_set_rate(pwm->clk_div, tin_rate);

} else

tin_rate = clk_get_rate(pwm->clk);

pwm->period_ns = period_ns;

pwm_dbg(pwm, "tin_rate=%lu\n", tin_rate);

tin_ns = NS_IN_HZ / tin_rate;

tcnt = period_ns / tin_ns; //根据周期求TCNT,n=To/Ti

} else

tin_ns = NS_IN_HZ / clk_get_rate(pwm->clk);

tcmp = duty_ns / tin_ns; //根据空占比求TCMP

tcmp = tcnt - tcmp; //根据占空比求TCMP

if (tcmp == tcnt)

tcmp--;

pwm_dbg(pwm, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt);

if (tcmp < 0)

tcmp = 0;

local_irq_save(flags);

__raw_writel(tcmp, S3C2410_TCMPB(pwm->pwm_id)); //写入TCMP

__raw_writel(tcnt, S3C2410_TCNTB(pwm->pwm_id)); //写入TCNT

tcon = __raw_readl(S3C2410_TCON);

tcon |= pwm_tcon_manulupdate(pwm);

tcon |= pwm_tcon_autoreload(pwm); //自动加载

__raw_writel(tcon, S3C2410_TCON);

tcon &= ~pwm_tcon_manulupdate(pwm); //更新TCNT和TCMP

__raw_writel(tcon, S3C2410_TCON);

local_irq_restore(flags);

return 0;

}

下面说说这个周期是怎么设计的

我们定时器的输出频率fi=PCLK/(prescaler value+1)/(divider value),这个可以获得确定值

我们需要写入一个初值n给TCNT,这样就可以获得一个频率,为什么呢?

根据初值n=fi/fo,那么n=To/Ti

所以当用户给pwm_config函数传递一个周期period_ns,其实就是To=period_ns

这样根据前面公式n=To/Ti= period_ns/fi,然后将这个初值n写入TCNT就可以改变周期了

接着我再补充说明下pwm_config函数里代码注释关于自动加载怎么回事?

定时器工作原理其实是TCNT的值在时钟到来时,减一计数,每次减一完后,拿当前TCNT与TCMP比较,如果TCNT=TCMP,那么信号电平反向输出,然后TCNT继续减一计数,知道TCNT减到零后,如果有自动加载功能那么此时将由TCNTB把计数初值再次写给TCNTP,同时TCMPB把比较值给TCMP,这样就完成一次初值重装,然后继续进行计数。我们给这种加载模式起了个名字叫双缓冲机制,其中TCMPB和TCNTB就是Buffer缓存。

前面说pwm.c集驱动和设备于一体,那么下面我们看看设备相关的代码

#define TIMER_RESOURCE_SIZE (1)

#define TIMER_RESOURCE(_tmr, _irq) \

(struct resource [TIMER_RESOURCE_SIZE]) { \

[0] = { \

.start = _irq, \

.end = _irq, \

.flags = IORESOURCE_IRQ \

} \

}

#define DEFINE_S3C_TIMER(_tmr_no, _irq) \

.name = "s3c24xx-pwm", \

.id = _tmr_no, \

.num_resources = TIMER_RESOURCE_SIZE, \

.resource = TIMER_RESOURCE(_tmr_no, _irq), \

struct platform_device s3c_device_timer[] = {

[0] = { DEFINE_S3C_TIMER(0, IRQ_TIMER0) },

[1] = { DEFINE_S3C_TIMER(1, IRQ_TIMER1) },

[2] = { DEFINE_S3C_TIMER(2, IRQ_TIMER2) },

[3] = { DEFINE_S3C_TIMER(3, IRQ_TIMER3) },

[4] = { DEFINE_S3C_TIMER(4, IRQ_TIMER4) },

};

上面的代码就是设备部分代码,其实就是五个定时器的资源,我们把目光放在DEFINE_S3C_TIMER宏上,你会发现其设备名是"s3c24xx-pwm",而我们在pwm.c中定义的驱动名也是"s3c24xx-pwm",这样如果我们把设备注册到内核,那么设备"s3c24xx-pwm"和驱动"s3c24xx-pwm"就会匹配成功。所以如果你用到定时器0,那么你只要在BSP中添加s3c_device_timer[0]就可以了。我们现在做的是蜂鸣器驱动,使用的是Timer0定时器,我们就在mini2440的BSP文件mach-mini2440.c中添加如下代码

static struct platform_device *mini2440_devices[] __initdata = {

……

&s3c_device_timer[0], //添加

};

这样我们就分析完pwm核心层的代码了。

三.Backlight核心驱动

下面我们讲讲backlight子系统。背光子系统目录在/driver/video/backlight下,其中背光子系统核心代码是backlight.c

先查看/driver/video/backlight/Makefile

obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o

继续查看/driver/video/backlight/Kconfig

config BACKLIGHT_CLASS_DEVICE

tristate "Lowlevel Backlight controls"

depends on BACKLIGHT_LCD_SUPPORT

default m

所以配置内核make menuconfig时,需要选中这一项。

下面看backlight背光的核心代码backlight.c

static int __init backlight_class_init(void)

{

backlight_class = class_create(THIS_MODULE, "backlight"); //注册backlight类

if (IS_ERR(backlight_class)) {

printk(KERN_WARNING "Unable to create backlight class; errno = %ld\n",

PTR_ERR(backlight_class));

return PTR_ERR(backlight_class);

}

backlight_class->dev_attrs = bl_device_attributes; //添加类属性

backlight_class->suspend = backlight_suspend;

backlight_class->resume = backlight_resume;

return 0;

}

我们知道backlight背光子系统的主要就是靠这个类属性,当我们设置背光值就是向类属性中某个成员写背光值,这个类属性就是给用户的一种接口,我们重点看看

#define __ATTR(_name,_mode,_show,_store) { \

.attr = {.name = __stringify(_name), .mode = _mode }, \

.show = _show, \

.store = _store, \

}

static struct device_attribute bl_device_attributes[] = {

__ATTR(bl_power, 0644, backlight_show_power, backlight_store_power),

__ATTR(brightness, 0644, backlight_show_brightness,

backlight_store_brightness),

__ATTR(actual_brightness, 0444, backlight_show_actual_brightness,

NULL),

__ATTR(max_brightness, 0444, backlight_show_max_brightness, NULL),

__ATTR_NULL,

};

很明显,在backlight类中我们创建了bl_power,brightness,actural_brightness,max_brightness四个成员,其中brightness是当前亮度,max_brightness是最大亮度。当用户层通过cat或者echo命令就会触发这些成员。对于这些属性的读写函数,我们先看看读的函数backlight_show_max_brightness吧

static ssize_t backlight_show_max_brightness(struct device *dev,

struct device_attribute *attr, char *buf)

{

struct backlight_device *bd = to_backlight_device(dev);

return sprintf(buf, "%d\n", bd->props.max_brightness); //输出最大亮度

}

这个函数很简单,但是重点是引入了几个backlight背光子系统的几个重要的数据结构,我们好好学习下。

首先是backlight背光子系统的设备结构体backlight_device

struct backlight_device {

struct backlight_properties props; //背光属性

struct mutex update_lock;

struct mutex ops_lock;

struct backlight_ops *ops; //背光操作函数,类似于file_operations

struct notifier_block fb_notif;

struct device dev; //内嵌设备

};

下面先看看背光属性结构体backlight_properties

struct backlight_properties {

int brightness; //当前背光值

int max_brightness; //最大背光值

int power;

int fb_blank;

unsigned int state;

};

再看看背光操作函数结构体

struct backlight_ops {

unsigned int options;

#define BL_CORE_SUSPENDRESUME (1 << 0)

int (*update_status)(struct backlight_device *); //改变背光状态

int (*get_brightness)(struct backlight_device *); //获取背光值

int (*check_fb)(struct fb_info *);

};

好了,我们继续看backlight类属性中写的函数,例如设置当前背光值函数backlight_store_brightness吧

static ssize_t backlight_store_brightness(struct device *dev,

struct device_attribute *attr, const char *buf, size_t count)

{

int rc;

struct backlight_device *bd = to_backlight_device(dev);

unsigned long brightness;

rc = strict_strtoul(buf, 0, &brightness);

if (rc)

return rc;

rc = -ENXIO;

mutex_lock(&bd->ops_lock);

if (bd->ops) {

if (brightness > bd->props.max_brightness)

rc = -EINVAL;

else {

pr_debug("backlight: set brightness to %lu\n",

brightness);

bd->props.brightness =brightness; //传入背光值

backlight_update_status(bd); //调用backlight_update_status设备背光值

rc = count;

}

}

mutex_unlock(&bd->ops_lock);

backlight_generate_event(bd, BACKLIGHT_UPDATE_SYSFS);

return rc;

}

跟踪backlight_update_status

static inline void backlight_update_status(struct backlight_device *bd)

{

mutex_lock(&bd->update_lock);

if (bd->ops && bd->ops->update_status)

bd->ops->update_status(bd); //调用背光操作函数中改变背光状态函数update_status

mutex_unlock(&bd->update_lock);

}

对于这个backlight背光核心层驱动backlight.c,剩下的就是这个pwm.c给我们提供了哪些接口函数了。

struct backlight_device *backlight_device_register(const char *name,

struct device *parent, void *devdata, struct backlight_ops *ops)

void backlight_device_unregister(struct backlight_device *bd)

EXPORT_SYMBOL(backlight_device_register); //注册背光设备

EXPORT_SYMBOL(backlight_device_unregister); //注销背光设备

这些接口很简单,就不细说了,这样我们的backlight子系统的核心层就介绍完了。

四.基于PWM&Backlight的蜂鸣器驱动

下面我们结合上面的PWM核心层和Backlight背光子系统核心层,根据基于pwm的背光驱动/driver/video/backlight/pwm_bl.c来修改成基于Mini2440的蜂鸣器驱动。

先查看/driver/video/backlight/Makefile

obj-$(CONFIG_BACKLIGHT_PWM) += pwm_bl.o

继续查看/driver/video/backlight/Kconfig

config BACKLIGHT_PWM

tristate "Generic PWM based Backlight Driver"

depends on BACKLIGHT_CLASS_DEVICE && HAVE_PWM

help

If you have a LCD backlight adjustable by PWM, say Y to enable

this driver.

我们的HAVE_PWM和BACKLIGHT_CLASS_DEVICE分别是在前面讲pwm核心和backlight核心时已经编译了,所以配置内核make menuconfig时,需要再选中"Generic PWM based Backlight Driver"这项。

好了,我们先把我们的蜂鸣器移植进去吧,首先我们知道蜂鸣器使用的是GPB0端口,该端口如果工作在TOU0模式,就可以通过设备定时器的TCNT和TCMP来控制定时器的波形而来。先打开mini2440的BSP文件mach-mini2440.c,如下添加

static struct platform_device s3c_backlight_device = {

.name = "pwm-backlight", //设备名

.dev = {

.parent = &s3c_device_timer[0].dev, //该设备基于pwm中的0号定时器

.platform_data = &s3c_backlight_data,

},

.id=0, //对应的就是pwm0

};

添加平台数据

static struct platform_pwm_backlight_data s3c_backlight_data = {

.pwm_id = 0, //对应的就是Timer0

.max_brightness = 1000, //最大亮度

.dft_brightness = 10 , //当前亮度

.pwm_period_ns = 800000, //这就是前面说的T0,即输出时钟周期

.init = s3c_bl_init, //端口初始化

};

注意到平台数据中定义了init函数,由于在蜂鸣器的初始化时,需要对GPB0设置为TOUT0模式,所以代码如下编写

static int s3c_bl_init(struct device *dev)

{

s3c2410_gpio_pullup(S3C2410_GPB(0),0); // GPB0不上拉

s3c2410_gpio_cfgpin(S3C2410_GPB(0),S3C2410_GPB0_TOUT0); // GPB0设置为TOUT0

return 0;

}

然后把这个s3c_backlight_device加入到mini2440_devices数组

static struct platform_device *mini2440_devices[] __initdata = {

……

&s3c_device_timer[0],

&s3c_backlight_device, //添加

};

最后添加头文件

#include <linux/pwm_backlight.h>

这样配置完后,进行make zImage生成zImage内核镜像。

好了,下面我们分析下基于pwm的背光驱动/driver/video/backlight/pwm_bl.c

static struct platform_driver pwm_backlight_driver = {

.driver = {

.name = "pwm-backlight", //驱动名

.owner = THIS_MODULE,

},

.probe = pwm_backlight_probe, //探测函数

.remove = pwm_backlight_remove,

.suspend = pwm_backlight_suspend,

.resume = pwm_backlight_resume,

};

static int __init pwm_backlight_init(void)

{

return platform_driver_register(&pwm_backlight_driver);

}

注意上面的pwm_backlight_driver中的驱动名"pwm-backlight"和我们刚才移植时添加的设备名"pwm-backlight"是一致的,这样设备和驱动就能匹配成功。下面看探测函数

static int pwm_backlight_probe(struct platform_device *pdev)

{

struct platform_pwm_backlight_data *data = pdev->dev.platform_data;

struct backlight_device *bl;

struct pwm_bl_data *pb; //本驱动的私有结构体

int ret;

if (!data) {

dev_err(&pdev->dev, "failed to find platform data\n");

return -EINVAL;

}

if (data->init) { //初始化端口,这个端口函数在BSP中定义

ret = data->init(&pdev->dev);

if (ret < 0)

return ret;

}

pb = kzalloc(sizeof(*pb), GFP_KERNEL); //分配pwm_bl_data空间

if (!pb) {

dev_err(&pdev->dev, "no memory for state\n");

ret = -ENOMEM;

goto err_alloc;

}

pb->period = data->pwm_period_ns; //获取周期

pb->notify = data->notify;

pb->pwm = pwm_request(data->pwm_id, "backlight"); //注册pwm设备

if (IS_ERR(pb->pwm)) {

dev_err(&pdev->dev, "unable to request PWM for backlight\n");

ret = PTR_ERR(pb->pwm);

goto err_pwm;

} else

dev_dbg(&pdev->dev, "got pwm for backlight\n");

bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev,

pb, &pwm_backlight_ops); //注册backlight设备

if (IS_ERR(bl)) {

dev_err(&pdev->dev, "failed to register backlight\n");

ret = PTR_ERR(bl);

goto err_bl;

}

bl->props.max_brightness = data->max_brightness;

bl->props.brightness = data->dft_brightness;

backlight_update_status(bl); //先点亮背光

platform_set_drvdata(pdev, bl); //设置bl为私有数据

return 0;

err_bl:

pwm_free(pb->pwm);

err_pwm:

kfree(pb);

err_alloc:

if (data->exit)

data->exit(&pdev->dev);

return ret;

}

对于这个驱动,我们重点关注的是注册backlight设备时传入的参数pwm_backlight_ops,因为我们之前分析backlight背光子系统时说过,背光设备结构体中有个操作背光的函数集合,在我们的pwm_bl.c中,就需要定义这个操作背光的函数集合,也就是pwm_backlight_ops

static struct backlight_ops pwm_backlight_ops = {

.update_status = pwm_backlight_update_status, //更新背光亮度

.get_brightness = pwm_backlight_get_brightness, //获取背光亮度

};

获取背光亮度函数pwm_backlight_get_brightness很简单,跟踪得到

static int pwm_backlight_get_brightness(struct backlight_device *bl)

{

return bl->props.brightness;

}

我们重点看更新背光亮度函数pwm_backlight_update_status

static int pwm_backlight_update_status(struct backlight_device *bl)

{

struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev);

int brightness = bl->props.brightness;

int max = bl->props.max_brightness;

if (bl->props.power != FB_BLANK_UNBLANK)

brightness = 0;

if (bl->props.fb_blank != FB_BLANK_UNBLANK)

brightness = 0;

if (pb->notify)

brightness = pb->notify(brightness);

if (brightness == 0) { //背光值为0,关闭背光

pwm_config(pb->pwm, 0, pb->period);

pwm_disable(pb->pwm);

} else { //调用pwm中的API设置背光

pwm_config(pb->pwm, brightness * pb->period / max, pb->period);

pwm_enable(pb->pwm);

}

return 0;

}

好了,这样我们的pwm_bl.c也分析完了。在使用backlight子系统的时候,我们只需要在probe函数中注册pwm和backlight设备,然后定义背光操作函数集合即可。

五.驱动测试

实验环境:内核linux2.6.32.2,arm-linux-gcc交叉编译器,mini2440开发板

下面我们进行对上面的驱动进行测试,按照上面的步骤操作,将上文已经编译好的zImage烧入开发板,通过超级终端控制,能控制蜂鸣器的发出的声音频率。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。