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
},
离线
/*
* 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
{
// DAC_DPC = 0x00,
// DAC_FIFOC = 0x04,
// DAC_FIFOS = 0x08,
// DAC_TXDATA = 0x0c,
ADC_FIFOC = 0x10,
ADC_FIFOS = 0x14,
ADC_RXDATA = 0x18,
// DAC_MIXER_CTRL = 0x20,
ADC_MIXER_CTRL = 0x24,
ADDA_TUNE = 0x28,
// BIAS_DA16_CAL_CTRL0 = 0x2C,
// BIAS_DA16_CAL_CTRL1 = 0x34,
// DAC_CNT = 0x40,
ADC_CNT = 0x44,
// DAC_DG = 0x48,
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;
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.
// bits of channel in User Manual is wrong.
virtual_addr_t ADDR_MIXEL_CTRL = pdat->virt + ADC_MIXER_CTRL;
u32_t mixerReg = read32(ADDR_MIXEL_CTRL) & ~(0x1f << 8);
switch (channel) {
case ADC_CHANNEL_FMINL:
write32(ADDR_MIXEL_CTRL, mixerReg | (1 << 11));
break;
case ADC_CHANNEL_FMINR:
write32(ADDR_MIXEL_CTRL, mixerReg | (1 << 10));
break;
case ADC_CHANNEL_LINL:
write32(ADDR_MIXEL_CTRL, mixerReg | (1 << 12));
break;
default:
// mute all
break;
}
// FIFO flush
write32(pdat->virt + ADC_FIFOC, read32(pdat->virt + ADC_FIFOC) | (1 << 0));
const uint32_t WAIT_STABLE = 500;
udelay(WAIT_STABLE); // wait for internal filter to be stable
// 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 =
(0 << 29) // 48kHz,make higher for better speed
| (1 << 28) // digital enable
| (0 << 24) // mode 0
| (0 << 17) // delay after enable
| (0 << 16) // delay function
| (0xf << 8) // default
| (0 << 7) // stereo
| (0 << 6) // 16-bit
;
write32(pdat->virt + ADC_FIFOC, regVal);
regVal =
(1 << 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
| (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);
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);
pdat->virt = virt;
pdat->clk = strdup(clk);
pdat->reset = dt_read_int(n, "reset", -1);
adc->name = alloc_device_name(dt_read_name(n), -1);
adc->vreference = 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);
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);
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);
感谢分享!
在线
之前对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
},
离线
@Quotation @晕哥你们好!请问下F1C100S的KEYADC 可以当普通ADC用吗?比如做电量检测或者温度传感器检测?我直接对寄存器做了些配置,定时1S读数据寄存器但是每次读到的都是63。
void keyadc_irq_handle(void);
void keyadc_init(void)
{
KEYADC->KEYADC_CTRL = 0x00402101;
KEYADC->KEYADC_INTC = 0x00000000;
}
void time1_irq_handle(void)
{
if(nnnnn == 0)
{
nnnnn = 1;
gpio_direction_output(GPIO_PORT_E,GPIO_PIN_5,0);
}
else
{
nnnnn = 0;
gpio_direction_output(GPIO_PORT_E,GPIO_PIN_5,1);
}
rt_kprintf("adc = %4d", KEYADC->KEYADC_DATA);
TIM->TMR_IRQ_STA |= 0x00000002;
}
离线
好的 晕哥,我是看寄存器操作的,之前看xboot的里面都是用了中断的,我可以直接配置完后定时去读数据寄存器吧。
离线
在线
好的,谢谢你啦@晕哥!!!
离线
@Quotation @晕哥你们好!请问下F1C100S的KEYADC 可以当普通ADC用吗?比如做电量检测或者温度传感器检测?我直接对寄存器做了些配置,定时1S读数据寄存器但是每次读到的都是63。
可以,如晕哥所说,就是6bit精度有点低。我是用KEYADC做电量检测用,不用IRQ,IRQ是给做按键功能用的。每次需要值的时候读一下寄存器就好。
离线
这个好像就一路ADC吧,还是6BIT. STM32都好多路,为什么全志不多加几路呀
离线