您尚未登录。

楼主 #1 2018-10-10 14:30:18

Quotation
会员
注册时间: 2018-10-04
已发帖子: 296
积分: 264.5

贡献个F1C100s ADC驱动(for XBOOT)

F1C100s没有通用的ADC,所以用音频ADC撸了个通用ADC的驱动。
支持3路channel,分别对应FMINL,FMINR,LINL引脚。只支持单次采样,不是用来录制音频的。产品里需要电量检测什么的可以用。

下载代码:adc-f1c100s.c,添加到mach-f1c100s/driver;

再在设备树添加:

"clk-gate@0x01c20068": {"parent": "apb1", "name": "gate-bus-audiocodec", "shift": 0, "invert": false },
"clk-setting@0": {"clocks": [{"name": "gate-bus-audiocodec", "enable": true}]},
"clk-gate@0x01c20140": {"parent": "pll-audio", "name": "gate-sclk-audiocodec", "shift": 31, "invert": false },
"clk-link": { "parent": "gate-sclk-audiocodec", "name": "link-audiocodec" },

"adc-f1c100s@0x01c23c00": {
    "clock-name": "link-audiocodec",
    "reset": 64
},

离线

楼主 #4 2018-11-09 09:18:00

Quotation
会员
注册时间: 2018-10-04
已发帖子: 296
积分: 264.5

Re: 贡献个F1C100s ADC驱动(for XBOOT)

之前对ADC理解有误,现在修正了bug,更新一下代码。上面的代码作废。(话说本站不能编辑帖子吗?)
主要是对FIFO的理解:ADC向FIFO写入数据,写满后如果一直不读,新数据是被丢弃的,不会把旧数据挤走。所以要先清FIFO,等待下一帧数据到来再读取。

driver里加入adc-f1c100s.c:

/*
 * driver/adc-f1c100s.c
 *
 * F1C100s ADC driver for XBOOT.
 * Use Audio ADCs as generic ADC.
 *
 */

#include <xboot.h>
#include <clk/clk.h>
#include <reset/reset.h>
#include <adc/adc.h>

enum
{
	ADC_FIFOC			= 0x10,
	ADC_FIFOS			= 0x14,
	ADC_RXDATA			= 0x18,
	ADC_MIXER_CTRL		= 0x24,
	ADDA_TUNE			= 0x28,
	ADC_CNT				= 0x44,
	ADC_DG				= 0x4c,
	// ADC_DAP_CTR			= 0x70,
	// ADC_DAP_LCTR		= 0x74,
	// ADC_DAP_RCTR		= 0x78,
	// ADC_DAP_PARA		= 0x7C,
	// ADC_DAP_LAC			= 0x80,
	// ADC_DAP_LDAT		= 0x84,
	// ADC_DAP_RAC			= 0x88,
	// ADC_DAP_RDAT		= 0x8C,
	// ADC_DAP_HPFC		= 0x90,
	// ADC_DAP_LINAC		= 0x94,
	// ADC_DAP_RINAC		= 0x98,
	// ADC_DAP_ORT			= 0x9c,
};

enum
{
    ADC_CHANNEL_FMINL = 0,
    ADC_CHANNEL_FMINR,
    ADC_CHANNEL_LINL,
    // ADC_CHANNEL_MICIN,

    ADC_CHANNEL_COUNT
};

struct adc_f1c100s_pdata_t
{
    virtual_addr_t virt;
	char * clk;
	char * sclk;
	int reset;
};

static u32_t adc_f1c100s_read(struct adc_t *adc, int channel)
{
    struct adc_f1c100s_pdata_t *pdat = (struct adc_f1c100s_pdata_t *)adc->priv;

    // unmute channel.
    virtual_addr_t ADDR_MIXER_CTRL = pdat->virt + ADC_MIXER_CTRL;
    u32_t mixerReg = read32(ADDR_MIXER_CTRL) & ~(0x1f << 8);
	switch (channel) {
        case ADC_CHANNEL_FMINL:
            write32(ADDR_MIXER_CTRL, mixerReg | (1 << 12));
            break;
        case ADC_CHANNEL_FMINR:
            write32(ADDR_MIXER_CTRL, mixerReg | (1 << 11));
            break;
        case ADC_CHANNEL_LINL:
            write32(ADDR_MIXER_CTRL, mixerReg | (1 << 10));
            break;
        default:
            // mute all
            break;
    }

    const uint32_t WAIT_STABLE = 400;
    udelay(WAIT_STABLE);  // wait for internal filter to be stable

	// FIFO flush
	write32(pdat->virt + ADC_FIFOC, read32(pdat->virt + ADC_FIFOC) | (1 << 0));
	while (((read32(pdat->virt + ADC_FIFOS) >> 23) & 1) == 0) {}

    // by default, 0V = -24000 and VRA = 0.
    // make 0V = 0 and vreference = 65535.
	int val = (int16_t)(read32(pdat->virt + ADC_RXDATA) >> 16);
    val += 24000;
    if (val < 0) {
        val = 0;
    }
    return (u32_t)val;
}

static void adc_f1c100s_config(struct adc_t *adc)
{
	struct adc_f1c100s_pdata_t * pdat = (struct adc_f1c100s_pdata_t*)adc->priv;

	u32_t regVal = 
		(0u << 29)      // 48kHz,higher for better sampling speed
		| (1 << 28)     // digital enable
        | (0 << 24)     // mode 0
        | (0 << 17)     // delay after enable
        | (0 << 16)     // delay function
        | (0xf << 8)    // default
        | (1 << 7)      // mono
        | (0 << 6)      // 16-bit
        ;
    write32(pdat->virt + ADC_FIFOC, regVal);

    regVal = 
        (1u << 31)      // analog enable
        | (3 << 24)     // mic gain
        | (0 << 21)     // linein gain
        | (3 << 16)     // adc gain = 0
        | (1 << 14)     // COS slop time
        | (0 << 8)      // all mute
        | (1 << 7)      // PA speed fast
        | (4 << 0)      // default
        ;
    write32(pdat->virt + ADC_MIXER_CTRL, regVal);
}

static struct device_t * adc_f1c100s_probe(struct driver_t * drv, struct dtnode_t * n)
{
	struct adc_f1c100s_pdata_t * pdat;
	struct adc_t * adc;
	struct device_t * dev;
	virtual_addr_t virt = phys_to_virt(dt_read_address(n));
	char * clk = dt_read_string(n, "clock-name", NULL);
	char * sclk = dt_read_string(n, "sclk-name", NULL);

	pdat = malloc(sizeof(struct adc_f1c100s_pdata_t));
	if(!pdat)
		return FALSE;

	adc = malloc(sizeof(struct adc_t));
	if (!adc)
	{
		free(pdat);
		return FALSE;
	}

	clk_enable(clk);
	clk_enable(sclk);
	pdat->virt = virt;
	pdat->clk = strdup(clk);
	pdat->sclk = strdup(sclk);
	pdat->reset = dt_read_int(n, "reset", -1);

	adc->name = alloc_device_name(dt_read_name(n), -1);
	adc->vreference = dt_read_int(n, "vref", 2800000); // (AVCC * 1000000)
	adc->resolution = 16;
    adc->nchannel = ADC_CHANNEL_COUNT;
    adc->read = adc_f1c100s_read;
	adc->priv = pdat;

	if(pdat->reset >= 0)
		reset_deassert(pdat->reset);

    adc_f1c100s_config(adc);

	if (!register_adc(&dev, adc))
	{
		clk_disable(pdat->clk);
		free(pdat->clk);
		clk_disable(pdat->sclk);
		free(pdat->sclk);

		free_device_name(adc->name);
		free(adc->priv);
		free(adc);
		return NULL;
	}
	dev->driver = drv;

	return dev;
}

static void adc_f1c100s_remove(struct device_t * dev)
{
	struct adc_t * adc = (struct adc_t *)dev->priv;
	struct adc_f1c100s_pdata_t * pdat = (struct adc_f1c100s_pdata_t *)adc->priv;

	if(adc && unregister_adc(adc))
	{
		clk_disable(pdat->clk);
		free(pdat->clk);
		clk_disable(pdat->sclk);
		free(pdat->sclk);

		free_device_name(adc->name);
		free(adc->priv);
		free(adc);
	}
}

static void adc_f1c100s_suspend(struct device_t * dev)
{
}

static void adc_f1c100s_resume(struct device_t * dev)
{
}

static struct driver_t adc_f1c100s = {
	.name		= "adc-f1c100s",
	.probe		= adc_f1c100s_probe,
	.remove		= adc_f1c100s_remove,
	.suspend	= adc_f1c100s_suspend,
	.resume		= adc_f1c100s_resume,
};

static __init void adc_f1c100s_driver_init(void)
{
	register_driver(&adc_f1c100s);
}

static __exit void adc_f1c100s_driver_exit(void)
{
	unregister_driver(&adc_f1c100s);
}

driver_initcall(adc_f1c100s_driver_init);
driver_exitcall(adc_f1c100s_driver_exit);

设备树里加入:

	"clk-gate@0x01c20068": {"parent": "apb1", "name": "gate-bus-audiocodec", "shift": 0, "invert": false },
	"clk-gate@0x01c20140": {"parent": "pll-audio", "name": "gate-sclk-audiocodec", "shift": 31, "invert": false },
	"clk-link": { "parent": "gate-bus-audiocodec", "name": "link-adc" },
	"clk-link": { "parent": "gate-sclk-audiocodec", "name": "link-sclk-adc" },

	"adc-f1c100s@0x01c23c00": {
		"clock-name": "link-adc",
		"sclk-name": "link-sclk-adc",
		"reset": 64,
		"vref": 2800000
	},

离线

楼主 #11 2019-01-18 20:09:11

Quotation
会员
注册时间: 2018-10-04
已发帖子: 296
积分: 264.5

Re: 贡献个F1C100s ADC驱动(for XBOOT)

ZSB 说:

@Quotation @晕哥你们好!请问下F1C100S的KEYADC 可以当普通ADC用吗?比如做电量检测或者温度传感器检测?我直接对寄存器做了些配置,定时1S读数据寄存器但是每次读到的都是63。

可以,如晕哥所说,就是6bit精度有点低。我是用KEYADC做电量检测用,不用IRQ,IRQ是给做按键功能用的。每次需要值的时候读一下寄存器就好。

离线

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn