| /* |
| * Freescale Asynchronous Sample Rate Converter (ASRC) driver |
| * |
| * Copyright 2008-2014 Freescale Semiconductor, Inc. All Rights Reserved. |
| * |
| * This file is licensed under the terms of the GNU General Public License |
| * version 2. This program is licensed "as is" without any warranty of any |
| * kind, whether express or implied. |
| */ |
| |
| #include <linux/clk.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/sched.h> |
| #include <linux/regmap.h> |
| #include <linux/module.h> |
| #include <linux/proc_fs.h> |
| #include <linux/pagemap.h> |
| #include <linux/interrupt.h> |
| #include <linux/miscdevice.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/of_platform.h> |
| #include <linux/platform_data/dma-imx.h> |
| |
| #include <linux/mxc_asrc.h> |
| |
| #define ASRC_PROC_PATH "driver/asrc" |
| |
| #define ASRC_RATIO_DECIMAL_DEPTH 26 |
| |
| #define pair_err(fmt, ...) \ |
| dev_err(asrc->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) |
| |
| #define pair_dbg(fmt, ...) \ |
| dev_dbg(asrc->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) |
| |
| DEFINE_SPINLOCK(data_lock); |
| DEFINE_SPINLOCK(pair_lock); |
| |
| /* Sample rates are aligned with that defined in pcm.h file */ |
| static const unsigned char asrc_process_table[][8][2] = { |
| /* 32kHz 44.1kHz 48kHz 64kHz 88.2kHz 96kHz 176kHz 192kHz */ |
| {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 5512Hz */ |
| {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 8kHz */ |
| {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 11025Hz */ |
| {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 16kHz */ |
| {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 22050Hz */ |
| {{0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0},}, /* 32kHz */ |
| {{0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, /* 44.1kHz */ |
| {{0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, /* 48kHz */ |
| {{1, 2}, {0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0},}, /* 64kHz */ |
| {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, /* 88.2kHz */ |
| {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, /* 96kHz */ |
| {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, /* 176kHz */ |
| {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, /* 192kHz */ |
| }; |
| |
| static struct asrc_data *asrc; |
| |
| /* |
| * The following tables map the relationship between asrc_inclk/asrc_outclk in |
| * mxc_asrc.h and the registers of ASRCSR |
| */ |
| static unsigned char input_clk_map_v1[] = { |
| 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, |
| }; |
| |
| static unsigned char output_clk_map_v1[] = { |
| 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, |
| }; |
| |
| /* V2 uses the same map for input and output */ |
| static unsigned char input_clk_map_v2[] = { |
| /* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */ |
| 0x0, 0x1, 0x2, 0x7, 0x4, 0x5, 0x6, 0x3, 0x8, 0x9, 0xa, 0xb, 0xc, 0xf, 0xe, 0xd, |
| }; |
| |
| static unsigned char output_clk_map_v2[] = { |
| /* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */ |
| 0x8, 0x9, 0xa, 0x7, 0xc, 0x5, 0x6, 0xb, 0x0, 0x1, 0x2, 0x3, 0x4, 0xf, 0xe, 0xd, |
| }; |
| |
| static unsigned char *input_clk_map, *output_clk_map; |
| |
| enum mxc_asrc_type { |
| IMX35_ASRC, |
| IMX53_ASRC, |
| }; |
| |
| static const struct platform_device_id mxc_asrc_devtype[] = { |
| { |
| .name = "imx35-asrc", |
| .driver_data = IMX35_ASRC, |
| }, { |
| .name = "imx53-asrc", |
| .driver_data = IMX53_ASRC, |
| }, { |
| /* sentinel */ |
| } |
| }; |
| MODULE_DEVICE_TABLE(platform, mxc_asrc_devtype); |
| |
| static const struct of_device_id fsl_asrc_ids[] = { |
| { |
| .compatible = "fsl,imx35-asrc", |
| .data = &mxc_asrc_devtype[IMX35_ASRC], |
| }, { |
| .compatible = "fsl,imx53-asrc", |
| .data = &mxc_asrc_devtype[IMX53_ASRC], |
| }, { |
| /* sentinel */ |
| } |
| }; |
| MODULE_DEVICE_TABLE(of, fsl_asrc_ids); |
| |
| |
| #ifdef DEBUG |
| u32 asrc_reg[] = { |
| REG_ASRCTR, |
| REG_ASRIER, |
| REG_ASRCNCR, |
| REG_ASRCFG, |
| REG_ASRCSR, |
| REG_ASRCDR1, |
| REG_ASRCDR2, |
| REG_ASRSTR, |
| REG_ASRRA, |
| REG_ASRRB, |
| REG_ASRRC, |
| REG_ASRPM1, |
| REG_ASRPM2, |
| REG_ASRPM3, |
| REG_ASRPM4, |
| REG_ASRPM5, |
| REG_ASRTFR1, |
| REG_ASRCCR, |
| REG_ASRIDRHA, |
| REG_ASRIDRLA, |
| REG_ASRIDRHB, |
| REG_ASRIDRLB, |
| REG_ASRIDRHC, |
| REG_ASRIDRLC, |
| REG_ASR76K, |
| REG_ASR56K, |
| REG_ASRMCRA, |
| REG_ASRFSTA, |
| REG_ASRMCRB, |
| REG_ASRFSTB, |
| REG_ASRMCRC, |
| REG_ASRFSTC, |
| REG_ASRMCR1A, |
| REG_ASRMCR1B, |
| REG_ASRMCR1C, |
| }; |
| |
| static void dump_regs(void) |
| { |
| u32 reg, val; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(asrc_reg); i++) { |
| reg = asrc_reg[i]; |
| regmap_read(asrc->regmap, reg, &val); |
| dev_dbg(asrc->dev, "REG addr=0x%x val=0x%x\n", reg, val); |
| } |
| } |
| #else |
| static void dump_regs(void) {} |
| #endif |
| |
| /* Only used for Ideal Ratio mode */ |
| static int asrc_set_clock_ratio(enum asrc_pair_index index, |
| int inrate, int outrate) |
| { |
| unsigned long val = 0; |
| int integ, i; |
| |
| if (outrate == 0) { |
| dev_err(asrc->dev, "wrong output sample rate: %d\n", outrate); |
| return -EINVAL; |
| } |
| |
| /* Formula: r = (1 << ASRC_RATIO_DECIMAL_DEPTH) / outrate * inrate; */ |
| for (integ = 0; inrate >= outrate; integ++) |
| inrate -= outrate; |
| |
| val |= (integ << ASRC_RATIO_DECIMAL_DEPTH); |
| |
| for (i = 1; i <= ASRC_RATIO_DECIMAL_DEPTH; i++) { |
| if ((inrate * 2) >= outrate) { |
| val |= (1 << (ASRC_RATIO_DECIMAL_DEPTH - i)); |
| inrate = inrate * 2 - outrate; |
| } else |
| inrate = inrate << 1; |
| |
| if (inrate == 0) |
| break; |
| } |
| |
| regmap_write(asrc->regmap, REG_ASRIDRL(index), val); |
| regmap_write(asrc->regmap, REG_ASRIDRH(index), (val >> 24)); |
| |
| return 0; |
| } |
| |
| /* Corresponding to asrc_process_table */ |
| static int supported_input_rate[] = { |
| 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, |
| 96000, 176400, 192000, |
| }; |
| |
| static int supported_output_rate[] = { |
| 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000, |
| }; |
| |
| static int asrc_set_process_configuration(enum asrc_pair_index index, |
| int inrate, int outrate) |
| { |
| int in, out; |
| |
| for (in = 0; in < ARRAY_SIZE(supported_input_rate); in++) { |
| if (inrate == supported_input_rate[in]) |
| break; |
| } |
| |
| if (in == ARRAY_SIZE(supported_input_rate)) { |
| dev_err(asrc->dev, "unsupported input sample rate: %d\n", in); |
| return -EINVAL; |
| } |
| |
| for (out = 0; out < ARRAY_SIZE(supported_output_rate); out++) { |
| if (outrate == supported_output_rate[out]) |
| break; |
| } |
| |
| if (out == ARRAY_SIZE(supported_output_rate)) { |
| dev_err(asrc->dev, "unsupported output sample rate: %d\n", out); |
| return -EINVAL; |
| } |
| |
| regmap_update_bits(asrc->regmap, REG_ASRCFG, |
| ASRCFG_PREMODx_MASK(index) | ASRCFG_POSTMODx_MASK(index), |
| ASRCFG_PREMOD(index, asrc_process_table[in][out][0]) | |
| ASRCFG_POSTMOD(index, asrc_process_table[in][out][1])); |
| |
| return 0; |
| } |
| |
| static int asrc_get_asrck_clock_divider(int samplerate) |
| { |
| unsigned int prescaler, divider, ratio, ra, i; |
| unsigned long bitclk; |
| |
| if (samplerate == 0) { |
| dev_err(asrc->dev, "invalid sample rate: %d\n", samplerate); |
| return -EINVAL; |
| } |
| |
| bitclk = clk_get_rate(asrc->asrck_clk); |
| |
| ra = bitclk / samplerate; |
| ratio = ra; |
| |
| /* Calculate the prescaler */ |
| for (i = 0; ratio > 8; i++) |
| ratio >>= 1; |
| |
| prescaler = i; |
| |
| /* Calculate the divider */ |
| divider = i ? (((ra + (1 << (i - 1)) - 1) >> i) - 1) : (ra - 1); |
| |
| /* The totally divider is (2 ^ prescaler) * divider */ |
| return (divider << ASRCDRx_AxCPx_WIDTH) + prescaler; |
| } |
| |
| int asrc_req_pair(int chn_num, enum asrc_pair_index *index) |
| { |
| int imax = 0, busy = 0, i, ret = 0; |
| unsigned long lock_flags; |
| struct asrc_pair *pair; |
| |
| spin_lock_irqsave(&data_lock, lock_flags); |
| |
| for (i = ASRC_PAIR_A; i < ASRC_PAIR_MAX_NUM; i++) { |
| pair = &asrc->asrc_pair[i]; |
| if (chn_num > pair->chn_max) { |
| imax++; |
| continue; |
| } else if (pair->active) { |
| busy++; |
| continue; |
| } |
| /* Save the current qualified pair */ |
| *index = i; |
| |
| /* Check if this pair is a perfect one */ |
| if (chn_num == pair->chn_max) |
| break; |
| } |
| |
| if (imax == ASRC_PAIR_MAX_NUM) { |
| dev_err(asrc->dev, "no pair could afford required channel number\n"); |
| ret = -EINVAL; |
| } else if (busy == ASRC_PAIR_MAX_NUM) { |
| dev_err(asrc->dev, "all pairs are busy now\n"); |
| ret = -EBUSY; |
| } else if (busy + imax >= ASRC_PAIR_MAX_NUM) { |
| dev_err(asrc->dev, "all affordable pairs are busy now\n"); |
| ret = -EBUSY; |
| } else { |
| pair = &asrc->asrc_pair[*index]; |
| pair->chn_num = chn_num; |
| pair->active = 1; |
| } |
| |
| spin_unlock_irqrestore(&data_lock, lock_flags); |
| |
| if (!ret) { |
| clk_prepare_enable(asrc->mem_clk); |
| clk_prepare_enable(asrc->ipg_clk); |
| clk_prepare_enable(asrc->asrck_clk); |
| clk_prepare_enable(asrc->dma_clk); |
| } |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(asrc_req_pair); |
| |
| void asrc_release_pair(enum asrc_pair_index index) |
| { |
| struct asrc_pair *pair = &asrc->asrc_pair[index]; |
| unsigned long lock_flags; |
| |
| spin_lock_irqsave(&data_lock, lock_flags); |
| |
| pair->active = 0; |
| pair->overload_error = 0; |
| |
| spin_unlock_irqrestore(&data_lock, lock_flags); |
| |
| /* Disable PAIR */ |
| regmap_update_bits(asrc->regmap, REG_ASRCTR, ASRCTR_ASRCEx_MASK(index), 0); |
| } |
| EXPORT_SYMBOL(asrc_release_pair); |
| |
| int asrc_config_pair(struct asrc_config *config) |
| { |
| u32 inrate = config->input_sample_rate, indiv; |
| u32 outrate = config->output_sample_rate, outdiv; |
| int ret, channels, index = config->pair; |
| unsigned long lock_flags; |
| |
| /* Set the channel number */ |
| spin_lock_irqsave(&data_lock, lock_flags); |
| asrc->asrc_pair[index].chn_num = config->channel_num; |
| spin_unlock_irqrestore(&data_lock, lock_flags); |
| |
| if (asrc->channel_bits > 3) |
| channels = config->channel_num; |
| else |
| channels = (config->channel_num + 1) / 2; |
| |
| /* Update channel number of current pair */ |
| regmap_update_bits(asrc->regmap, REG_ASRCNCR, |
| ASRCNCR_ANCx_MASK(index, asrc->channel_bits), |
| ASRCNCR_ANCx_set(index, channels, asrc->channel_bits)); |
| |
| /* Set the clock source */ |
| regmap_update_bits(asrc->regmap, REG_ASRCSR, |
| ASRCSR_AICSx_MASK(index) | ASRCSR_AOCSx_MASK(index), |
| ASRCSR_AICS(index, input_clk_map[config->inclk]) | |
| ASRCSR_AOCS(index, output_clk_map[config->outclk])); |
| |
| /* Default setting: Automatic selection for processing mode */ |
| regmap_update_bits(asrc->regmap, REG_ASRCTR, |
| ASRCTR_ATSx_MASK(index), ASRCTR_ATS(index)); |
| regmap_update_bits(asrc->regmap, REG_ASRCTR, ASRCTR_USRx_MASK(index), 0); |
| |
| /* Default Input Clock Divider Setting */ |
| switch (config->inclk & ASRCSR_AxCSx_MASK) { |
| case INCLK_SPDIF_RX: |
| indiv = ASRC_PRESCALER_SPDIF_RX; |
| break; |
| case INCLK_SPDIF_TX: |
| indiv = ASRC_PRESCALER_SPDIF_TX; |
| break; |
| case INCLK_ASRCK1_CLK: |
| indiv = asrc_get_asrck_clock_divider(inrate); |
| break; |
| default: |
| switch (config->input_word_width) { |
| case ASRC_WIDTH_16_BIT: |
| indiv = ASRC_PRESCALER_I2S_16BIT; |
| break; |
| case ASRC_WIDTH_24_BIT: |
| indiv = ASRC_PRESCALER_I2S_24BIT; |
| break; |
| default: |
| pair_err("unsupported input word width %d\n", |
| config->input_word_width); |
| return -EINVAL; |
| } |
| break; |
| } |
| |
| /* Default Output Clock Divider Setting */ |
| switch (config->outclk & ASRCSR_AxCSx_MASK) { |
| case OUTCLK_SPDIF_RX: |
| outdiv = ASRC_PRESCALER_SPDIF_RX; |
| break; |
| case OUTCLK_SPDIF_TX: |
| outdiv = ASRC_PRESCALER_SPDIF_TX; |
| break; |
| case OUTCLK_ASRCK1_CLK: |
| if ((config->inclk & ASRCSR_AxCSx_MASK) == INCLK_NONE) |
| outdiv = ASRC_PRESCALER_IDEAL_RATIO; |
| else |
| outdiv = asrc_get_asrck_clock_divider(outrate); |
| break; |
| default: |
| switch (config->output_word_width) { |
| case ASRC_WIDTH_16_BIT: |
| outdiv = ASRC_PRESCALER_I2S_16BIT; |
| break; |
| case ASRC_WIDTH_24_BIT: |
| outdiv = ASRC_PRESCALER_I2S_24BIT; |
| break; |
| default: |
| pair_err("unsupported output word width %d\n", |
| config->input_word_width); |
| return -EINVAL; |
| } |
| break; |
| } |
| |
| /* indiv and outdiv'd include prescaler's value, so add its MASK too */ |
| regmap_update_bits(asrc->regmap, REG_ASRCDR(index), |
| ASRCDRx_AOCPx_MASK(index) | ASRCDRx_AICPx_MASK(index) | |
| ASRCDRx_AOCDx_MASK(index) | ASRCDRx_AICDx_MASK(index), |
| ASRCDRx_AOCP(index, outdiv) | ASRCDRx_AICP(index, indiv)); |
| |
| /* Check whether ideal ratio is a must */ |
| switch (config->inclk & ASRCSR_AxCSx_MASK) { |
| case INCLK_NONE: |
| /* Clear ASTSx bit to use ideal ratio */ |
| regmap_update_bits(asrc->regmap, REG_ASRCTR, |
| ASRCTR_ATSx_MASK(index), 0); |
| |
| regmap_update_bits(asrc->regmap, REG_ASRCTR, |
| ASRCTR_IDRx_MASK(index) | ASRCTR_USRx_MASK(index), |
| ASRCTR_IDR(index) | ASRCTR_USR(index)); |
| |
| ret = asrc_set_clock_ratio(index, inrate, outrate); |
| if (ret) |
| return ret; |
| |
| ret = asrc_set_process_configuration(index, inrate, outrate); |
| if (ret) |
| return ret; |
| |
| break; |
| case INCLK_ASRCK1_CLK: |
| /* This case and default are both remained for v1 */ |
| if (inrate == 44100 || inrate == 88200) { |
| pair_err("unsupported sample rate %d by selected clock\n", |
| inrate); |
| return -EINVAL; |
| } |
| break; |
| default: |
| if ((config->outclk & ASRCSR_AxCSx_MASK) != OUTCLK_ASRCK1_CLK) |
| break; |
| |
| if (outrate == 44100 || outrate == 88200) { |
| pair_err("unsupported sample rate %d by selected clock\n", |
| outrate); |
| return -EINVAL; |
| } |
| break; |
| } |
| |
| /* Config input and output wordwidth */ |
| if (config->output_word_width == ASRC_WIDTH_8_BIT) { |
| pair_err("unsupported wordwidth for output: 8bit\n"); |
| pair_err("output only support: 16bit or 24bit\n"); |
| return -EINVAL; |
| } |
| |
| regmap_update_bits(asrc->regmap, REG_ASRMCR1(index), |
| ASRMCR1x_OW16_MASK | ASRMCR1x_IWD_MASK, |
| ASRMCR1x_OW16(config->output_word_width) | |
| ASRMCR1x_IWD(config->input_word_width)); |
| |
| /* Enable BUFFER STALL */ |
| regmap_update_bits(asrc->regmap, REG_ASRMCR(index), |
| ASRMCRx_BUFSTALLx_MASK, ASRMCRx_BUFSTALLx); |
| |
| /* Set Threshold for input and output FIFO */ |
| return asrc_set_watermark(index, ASRC_INPUTFIFO_THRESHOLD, |
| ASRC_INPUTFIFO_THRESHOLD); |
| } |
| EXPORT_SYMBOL(asrc_config_pair); |
| |
| int asrc_set_watermark(enum asrc_pair_index index, u32 in_wm, u32 out_wm) |
| { |
| if (in_wm > ASRC_FIFO_THRESHOLD_MAX || out_wm > ASRC_FIFO_THRESHOLD_MAX) { |
| pair_err("invalid watermark!\n"); |
| return -EINVAL; |
| } |
| |
| return regmap_update_bits(asrc->regmap, REG_ASRMCR(index), |
| ASRMCRx_EXTTHRSHx_MASK | ASRMCRx_INFIFO_THRESHOLD_MASK | |
| ASRMCRx_OUTFIFO_THRESHOLD_MASK, |
| ASRMCRx_EXTTHRSHx | ASRMCRx_INFIFO_THRESHOLD(in_wm) | |
| ASRMCRx_OUTFIFO_THRESHOLD(out_wm)); |
| } |
| EXPORT_SYMBOL(asrc_set_watermark); |
| |
| void asrc_start_conv(enum asrc_pair_index index) |
| { |
| int reg, retry, channels, i; |
| |
| regmap_update_bits(asrc->regmap, REG_ASRCTR, |
| ASRCTR_ASRCEx_MASK(index), ASRCTR_ASRCE(index)); |
| |
| /* Wait for status of initialization */ |
| for (retry = 10, reg = 0; !reg && retry; --retry) { |
| udelay(5); |
| regmap_read(asrc->regmap, REG_ASRCFG, ®); |
| reg &= ASRCFG_INIRQx_MASK(index); |
| } |
| |
| /* Set the input fifo to ASRC STALL level */ |
| regmap_read(asrc->regmap, REG_ASRCNCR, ®); |
| channels = ASRCNCR_ANCx_get(index, reg, asrc->channel_bits); |
| for (i = 0; i < channels * 4; i++) |
| regmap_write(asrc->regmap, REG_ASRDI(index), 0); |
| |
| /* Overload Interrupt Enable */ |
| regmap_write(asrc->regmap, REG_ASRIER, ASRIER_AOLIE); |
| } |
| EXPORT_SYMBOL(asrc_start_conv); |
| |
| void asrc_stop_conv(enum asrc_pair_index index) |
| { |
| regmap_update_bits(asrc->regmap, REG_ASRCTR, ASRCTR_ASRCEx_MASK(index), 0); |
| } |
| EXPORT_SYMBOL(asrc_stop_conv); |
| |
| void asrc_finish_conv(enum asrc_pair_index index) |
| { |
| clk_disable_unprepare(asrc->dma_clk); |
| clk_disable_unprepare(asrc->asrck_clk); |
| clk_disable_unprepare(asrc->ipg_clk); |
| clk_disable_unprepare(asrc->mem_clk); |
| } |
| EXPORT_SYMBOL(asrc_finish_conv); |
| |
| #define SET_OVERLOAD_ERR(index, err, msg) \ |
| do { \ |
| asrc->asrc_pair[index].overload_error |= err; \ |
| pair_dbg(msg); \ |
| } while (0) |
| |
| static irqreturn_t asrc_isr(int irq, void *dev_id) |
| { |
| enum asrc_pair_index index; |
| u32 status; |
| |
| regmap_read(asrc->regmap, REG_ASRSTR, &status); |
| |
| for (index = ASRC_PAIR_A; index < ASRC_PAIR_MAX_NUM; index++) { |
| if (asrc->asrc_pair[index].active == 0) |
| continue; |
| if (status & ASRSTR_ATQOL) |
| SET_OVERLOAD_ERR(index, ASRC_TASK_Q_OVERLOAD, |
| "Task Queue FIFO overload"); |
| if (status & ASRSTR_AOOL(index)) |
| SET_OVERLOAD_ERR(index, ASRC_OUTPUT_TASK_OVERLOAD, |
| "Output Task Overload"); |
| if (status & ASRSTR_AIOL(index)) |
| SET_OVERLOAD_ERR(index, ASRC_INPUT_TASK_OVERLOAD, |
| "Input Task Overload"); |
| if (status & ASRSTR_AODO(index)) |
| SET_OVERLOAD_ERR(index, ASRC_OUTPUT_BUFFER_OVERFLOW, |
| "Output Data Buffer has overflowed"); |
| if (status & ASRSTR_AIDU(index)) |
| SET_OVERLOAD_ERR(index, ASRC_INPUT_BUFFER_UNDERRUN, |
| "Input Data Buffer has underflowed"); |
| } |
| |
| /* Clean overload error */ |
| regmap_write(asrc->regmap, REG_ASRSTR, ASRSTR_AOLE); |
| |
| return IRQ_HANDLED; |
| } |
| |
| void asrc_get_status(struct asrc_status_flags *flags) |
| { |
| enum asrc_pair_index index = flags->index; |
| unsigned long lock_flags; |
| |
| spin_lock_irqsave(&data_lock, lock_flags); |
| |
| flags->overload_error = asrc->asrc_pair[index].overload_error; |
| |
| spin_unlock_irqrestore(&data_lock, lock_flags); |
| } |
| EXPORT_SYMBOL(asrc_get_status); |
| |
| u32 asrc_get_per_addr(enum asrc_pair_index index, bool in) |
| { |
| return asrc->paddr + (in ? REG_ASRDI(index) : REG_ASRDO(index)); |
| } |
| EXPORT_SYMBOL(asrc_get_per_addr); |
| |
| static int mxc_init_asrc(void) |
| { |
| /* Halt ASRC internal FP when input FIFO needs data for pair A, B, C */ |
| regmap_write(asrc->regmap, REG_ASRCTR, ASRCTR_ASRCEN); |
| |
| /* Disable interrupt by default */ |
| regmap_write(asrc->regmap, REG_ASRIER, 0x0); |
| |
| /* Default 2: 6: 2 channel assignment */ |
| regmap_update_bits(asrc->regmap, REG_ASRCNCR, |
| ASRCNCR_ANCx_MASK(ASRC_PAIR_A, asrc->channel_bits), |
| ASRCNCR_ANCx_set(ASRC_PAIR_A, 2, asrc->channel_bits)); |
| regmap_update_bits(asrc->regmap, REG_ASRCNCR, |
| ASRCNCR_ANCx_MASK(ASRC_PAIR_B, asrc->channel_bits), |
| ASRCNCR_ANCx_set(ASRC_PAIR_B, 6, asrc->channel_bits)); |
| regmap_update_bits(asrc->regmap, REG_ASRCNCR, |
| ASRCNCR_ANCx_MASK(ASRC_PAIR_C, asrc->channel_bits), |
| ASRCNCR_ANCx_set(ASRC_PAIR_C, 2, asrc->channel_bits)); |
| |
| /* Parameter Registers recommended settings */ |
| regmap_write(asrc->regmap, REG_ASRPM1, 0x7fffff); |
| regmap_write(asrc->regmap, REG_ASRPM2, 0x255555); |
| regmap_write(asrc->regmap, REG_ASRPM3, 0xff7280); |
| regmap_write(asrc->regmap, REG_ASRPM4, 0xff7280); |
| regmap_write(asrc->regmap, REG_ASRPM5, 0xff7280); |
| |
| /* Base address for task queue FIFO. Set to 0x7C */ |
| regmap_update_bits(asrc->regmap, REG_ASRTFR1, |
| ASRTFR1_TF_BASE_MASK, ASRTFR1_TF_BASE(0xfc)); |
| |
| /* Set the processing clock for 76KHz, 133M */ |
| regmap_write(asrc->regmap, REG_ASR76K, 0x06D6); |
| |
| /* Set the processing clock for 56KHz, 133M */ |
| return regmap_write(asrc->regmap, REG_ASR56K, 0x0947); |
| } |
| |
| #define ASRC_xPUT_DMA_CALLBACK(in) \ |
| ((in) ? asrc_input_dma_callback : asrc_output_dma_callback) |
| |
| static void asrc_input_dma_callback(void *data) |
| { |
| struct asrc_pair_params *params = (struct asrc_pair_params *)data; |
| |
| complete(¶ms->input_complete); |
| } |
| |
| static void asrc_output_dma_callback(void *data) |
| { |
| struct asrc_pair_params *params = (struct asrc_pair_params *)data; |
| |
| complete(¶ms->output_complete); |
| } |
| |
| static unsigned int asrc_get_output_FIFO_size(enum asrc_pair_index index) |
| { |
| u32 val; |
| |
| regmap_read(asrc->regmap, REG_ASRFST(index), &val); |
| |
| val &= ASRFSTx_OUTPUT_FIFO_MASK; |
| |
| return val >> ASRFSTx_OUTPUT_FIFO_SHIFT; |
| } |
| |
| static u32 asrc_read_one_from_output_FIFO(enum asrc_pair_index index) |
| { |
| u32 val; |
| |
| regmap_read(asrc->regmap, REG_ASRDO(index), &val); |
| |
| return val; |
| } |
| |
| static void asrc_read_output_FIFO(struct asrc_pair_params *params) |
| { |
| u32 *reg24 = params->output_last_period.dma_vaddr; |
| u16 *reg16 = params->output_last_period.dma_vaddr; |
| enum asrc_pair_index index = params->index; |
| u32 i, j, reg, size, t_size; |
| bool bit24 = false; |
| |
| if (params->output_word_width == ASRC_WIDTH_24_BIT) |
| bit24 = true; |
| |
| t_size = 0; |
| do { |
| size = asrc_get_output_FIFO_size(index); |
| for (i = 0; i < size; i++) { |
| for (j = 0; j < params->channel_nums; j++) { |
| reg = asrc_read_one_from_output_FIFO(index); |
| if (bit24) { |
| *(reg24) = reg; |
| reg24++; |
| } else { |
| *(reg16) = (u16)reg; |
| reg16++; |
| } |
| } |
| } |
| t_size += size; |
| } while (size); |
| |
| if (t_size > params->last_period_sample) |
| t_size = params->last_period_sample; |
| |
| params->output_last_period.length = t_size * params->channel_nums * 2; |
| if (bit24) |
| params->output_last_period.length *= 2; |
| } |
| |
| static void mxc_free_dma_buf(struct asrc_pair_params *params) |
| { |
| if (params->input_dma_total.dma_vaddr != NULL) { |
| kfree(params->input_dma_total.dma_vaddr); |
| params->input_dma_total.dma_vaddr = NULL; |
| } |
| |
| if (params->output_dma_total.dma_vaddr != NULL) { |
| kfree(params->output_dma_total.dma_vaddr); |
| params->output_dma_total.dma_vaddr = NULL; |
| } |
| |
| if (params->output_last_period.dma_vaddr) { |
| dma_free_coherent(asrc->dev, 1024 * params->last_period_sample, |
| params->output_last_period.dma_vaddr, |
| params->output_last_period.dma_paddr); |
| params->output_last_period.dma_vaddr = NULL; |
| } |
| } |
| |
| static int mxc_allocate_dma_buf(struct asrc_pair_params *params) |
| { |
| struct dma_block *input_a, *output_a, *last_period; |
| enum asrc_pair_index index = params->index; |
| |
| input_a = ¶ms->input_dma_total; |
| output_a = ¶ms->output_dma_total; |
| last_period = ¶ms->output_last_period; |
| |
| input_a->dma_vaddr = kzalloc(input_a->length, GFP_KERNEL); |
| if (!input_a->dma_vaddr) { |
| pair_err("failed to allocate input dma buffer\n"); |
| goto exit; |
| } |
| input_a->dma_paddr = virt_to_dma(NULL, input_a->dma_vaddr); |
| |
| output_a->dma_vaddr = kzalloc(output_a->length, GFP_KERNEL); |
| if (!output_a->dma_vaddr) { |
| pair_err("failed to allocate output dma buffer\n"); |
| goto exit; |
| } |
| output_a->dma_paddr = virt_to_dma(NULL, output_a->dma_vaddr); |
| |
| last_period->dma_vaddr = dma_alloc_coherent(asrc->dev, |
| 1024 * params->last_period_sample, |
| &last_period->dma_paddr, GFP_KERNEL); |
| if (!last_period->dma_vaddr) { |
| pair_err("failed to allocate last period buffer\n"); |
| goto exit; |
| } |
| |
| return 0; |
| |
| exit: |
| mxc_free_dma_buf(params); |
| |
| return -ENOBUFS; |
| } |
| |
| static struct dma_chan *imx_asrc_get_dma_channel(enum asrc_pair_index index, bool in) |
| { |
| char name[4]; |
| |
| sprintf(name, "%cx%c", in ? 'r' : 't', index + 'a'); |
| |
| return dma_request_slave_channel(asrc->dev, name); |
| } |
| |
| static int imx_asrc_dma_config(struct asrc_pair_params *params, |
| struct dma_chan *chan, u32 dma_addr, |
| void *buf_addr, u32 buf_len, bool in, |
| enum asrc_word_width word_width) |
| { |
| enum asrc_pair_index index = params->index; |
| struct dma_async_tx_descriptor *desc; |
| struct dma_slave_config slave_config; |
| enum dma_slave_buswidth buswidth; |
| struct scatterlist *sg; |
| unsigned int sg_nent, i; |
| int ret; |
| |
| if (in) { |
| sg = params->input_sg; |
| sg_nent = params->input_sg_nodes; |
| desc = params->desc_in; |
| } else { |
| sg = params->output_sg; |
| sg_nent = params->output_sg_nodes; |
| desc = params->desc_out; |
| } |
| |
| switch (word_width) { |
| case ASRC_WIDTH_16_BIT: |
| buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; |
| break; |
| case ASRC_WIDTH_24_BIT: |
| buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; |
| break; |
| default: |
| pair_err("invalid word width\n"); |
| return -EINVAL; |
| } |
| |
| slave_config.dma_request0 = 0; |
| slave_config.dma_request1 = 0; |
| |
| if (in) { |
| slave_config.direction = DMA_MEM_TO_DEV; |
| slave_config.dst_addr = dma_addr; |
| slave_config.dst_addr_width = buswidth; |
| slave_config.dst_maxburst = |
| params->input_wm * params->channel_nums; |
| } else { |
| slave_config.direction = DMA_DEV_TO_MEM; |
| slave_config.src_addr = dma_addr; |
| slave_config.src_addr_width = buswidth; |
| slave_config.src_maxburst = |
| params->output_wm * params->channel_nums; |
| } |
| ret = dmaengine_slave_config(chan, &slave_config); |
| if (ret) { |
| pair_err("failed to config dmaengine for %sput task: %d\n", |
| in ? "in" : "out", ret); |
| return -EINVAL; |
| } |
| |
| sg_init_table(sg, sg_nent); |
| switch (sg_nent) { |
| case 1: |
| sg_init_one(sg, buf_addr, buf_len); |
| break; |
| case 2: |
| case 3: |
| case 4: |
| for (i = 0; i < (sg_nent - 1); i++) |
| sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE, |
| ASRC_MAX_BUFFER_SIZE); |
| |
| sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE, |
| buf_len - ASRC_MAX_BUFFER_SIZE * i); |
| break; |
| default: |
| pair_err("invalid input DMA nodes number: %d\n", sg_nent); |
| return -EINVAL; |
| } |
| |
| ret = dma_map_sg(NULL, sg, sg_nent, slave_config.direction); |
| if (ret != sg_nent) { |
| pair_err("failed to map dma sg for %sput task\n", |
| in ? "in" : "out"); |
| return -EINVAL; |
| } |
| |
| desc = dmaengine_prep_slave_sg(chan, sg, sg_nent, |
| slave_config.direction, DMA_PREP_INTERRUPT); |
| if (!desc) { |
| pair_err("failed to prepare slave sg for %sput task\n", |
| in ? "in" : "out"); |
| return -EINVAL; |
| } |
| |
| if (in) { |
| params->desc_in = desc; |
| params->desc_in->callback = asrc_input_dma_callback; |
| } else { |
| params->desc_out = desc; |
| params->desc_out->callback = asrc_output_dma_callback; |
| } |
| |
| desc->callback = ASRC_xPUT_DMA_CALLBACK(in); |
| desc->callback_param = params; |
| |
| return 0; |
| } |
| |
| static int mxc_asrc_prepare_io_buffer(struct asrc_pair_params *params, |
| struct asrc_convert_buffer *pbuf, bool in) |
| { |
| enum asrc_pair_index index = params->index; |
| struct dma_chan *dma_channel; |
| enum asrc_word_width width; |
| unsigned int *dma_len, *sg_nodes, buf_len, wm; |
| void __user *buf_vaddr; |
| void *dma_vaddr; |
| u32 word_size, fifo_addr; |
| |
| if (in) { |
| dma_channel = params->input_dma_channel; |
| dma_vaddr = params->input_dma_total.dma_vaddr; |
| dma_len = ¶ms->input_dma_total.length; |
| width = params->input_word_width; |
| sg_nodes = ¶ms->input_sg_nodes; |
| wm = params->input_wm; |
| buf_vaddr = (void __user *)pbuf->input_buffer_vaddr; |
| buf_len = pbuf->input_buffer_length; |
| } else { |
| dma_channel = params->output_dma_channel; |
| dma_vaddr = params->output_dma_total.dma_vaddr; |
| dma_len = ¶ms->output_dma_total.length; |
| width = params->output_word_width; |
| sg_nodes = ¶ms->output_sg_nodes; |
| wm = params->last_period_sample; |
| buf_vaddr = (void __user *)pbuf->output_buffer_vaddr; |
| buf_len = pbuf->output_buffer_length; |
| } |
| |
| switch (width) { |
| case ASRC_WIDTH_24_BIT: |
| word_size = 4; |
| break; |
| case ASRC_WIDTH_16_BIT: |
| case ASRC_WIDTH_8_BIT: |
| word_size = 2; |
| break; |
| default: |
| pair_err("invalid %sput word size!\n", in ? "in" : "out"); |
| return -EINVAL; |
| } |
| |
| if (buf_len < word_size * params->channel_nums * wm) { |
| pair_err("%sput buffer size[%d] is too small!\n", |
| in ? "in" : "out", buf_len); |
| return -EINVAL; |
| } |
| |
| /* Copy origin data into input buffer */ |
| if (in && copy_from_user(dma_vaddr, buf_vaddr, buf_len)) |
| return -EFAULT; |
| |
| *dma_len = buf_len; |
| if (!in) |
| *dma_len -= wm * word_size * params->channel_nums; |
| |
| *sg_nodes = *dma_len / ASRC_MAX_BUFFER_SIZE + 1; |
| |
| fifo_addr = asrc_get_per_addr(params->index, in); |
| |
| return imx_asrc_dma_config(params, dma_channel, fifo_addr, dma_vaddr, |
| *dma_len, in, width); |
| } |
| |
| static int mxc_asrc_prepare_buffer(struct asrc_pair_params *params, |
| struct asrc_convert_buffer *pbuf) |
| { |
| enum asrc_pair_index index = params->index; |
| int ret; |
| |
| ret = mxc_asrc_prepare_io_buffer(params, pbuf, true); |
| if (ret) { |
| pair_err("failed to prepare input buffer: %d\n", ret); |
| return ret; |
| } |
| |
| ret = mxc_asrc_prepare_io_buffer(params, pbuf, false); |
| if (ret) { |
| pair_err("failed to prepare output buffer: %d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| int mxc_asrc_process_buffer_pre(struct completion *complete, |
| enum asrc_pair_index index, bool in) |
| { |
| if (!wait_for_completion_interruptible_timeout(complete, 10 * HZ)) { |
| pair_err("%sput dma task timeout\n", in ? "in" : "out"); |
| return -ETIME; |
| } else if (signal_pending(current)) { |
| pair_err("%sput task forcibly aborted\n", in ? "in" : "out"); |
| return -EBUSY; |
| } |
| |
| init_completion(complete); |
| |
| return 0; |
| } |
| |
| #define mxc_asrc_dma_umap(params) \ |
| do { \ |
| dma_unmap_sg(NULL, params->input_sg, params->input_sg_nodes, \ |
| DMA_MEM_TO_DEV); \ |
| dma_unmap_sg(NULL, params->output_sg, params->output_sg_nodes, \ |
| DMA_DEV_TO_MEM); \ |
| } while (0) |
| |
| int mxc_asrc_process_buffer(struct asrc_pair_params *params, |
| struct asrc_convert_buffer *pbuf) |
| { |
| enum asrc_pair_index index = params->index; |
| unsigned long lock_flags; |
| int ret; |
| |
| /* Check input task first */ |
| ret = mxc_asrc_process_buffer_pre(¶ms->input_complete, index, false); |
| if (ret) { |
| mxc_asrc_dma_umap(params); |
| return ret; |
| } |
| |
| /* ...then output task*/ |
| ret = mxc_asrc_process_buffer_pre(¶ms->output_complete, index, true); |
| if (ret) { |
| mxc_asrc_dma_umap(params); |
| return ret; |
| } |
| |
| mxc_asrc_dma_umap(params); |
| |
| pbuf->input_buffer_length = params->input_dma_total.length; |
| pbuf->output_buffer_length = params->output_dma_total.length; |
| |
| spin_lock_irqsave(&pair_lock, lock_flags); |
| if (!params->pair_hold) { |
| spin_unlock_irqrestore(&pair_lock, lock_flags); |
| return -EFAULT; |
| } |
| spin_unlock_irqrestore(&pair_lock, lock_flags); |
| |
| asrc_read_output_FIFO(params); |
| |
| if (copy_to_user((void __user *)pbuf->output_buffer_vaddr, |
| params->output_dma_total.dma_vaddr, |
| params->output_dma_total.length)) |
| return -EFAULT; |
| |
| pbuf->output_buffer_length += params->output_last_period.length; |
| |
| if (copy_to_user((void __user *)pbuf->output_buffer_vaddr + |
| params->output_dma_total.length, |
| params->output_last_period.dma_vaddr, |
| params->output_last_period.length)) |
| return -EFAULT; |
| |
| return 0; |
| } |
| |
| #ifdef ASRC_POLLING_WITHOUT_DMA |
| static void asrc_write_one_to_input_FIFO(enum asrc_pair_index index, u32 val) |
| { |
| regmap_write(asrc->regmap, REG_ASRDI(index), val); |
| } |
| |
| /* THIS FUNCTION ONLY EXISTS FOR DEBUGGING AND ONLY SUPPORTS TWO CHANNELS */ |
| static void asrc_polling_debug(struct asrc_pair_params *params) |
| { |
| enum asrc_pair_index index = params->index; |
| u32 *in24 = params->input_dma_total.dma_vaddr; |
| u32 dma_len = params->input_dma_total.length / (params->channel_nums * 4); |
| u32 size, i, j, t_size, reg; |
| u32 *reg24 = params->output_dma_total.dma_vaddr; |
| |
| t_size = 0; |
| |
| for (i = 0; i < dma_len; ) { |
| for (j = 0; j < 2; j++) { |
| asrc_write_one_to_input_FIFO(index, *in24); |
| in24++; |
| asrc_write_one_to_input_FIFO(index, *in24); |
| in24++; |
| i++; |
| } |
| udelay(50); |
| udelay(50 * params->output_sample_rate / params->input_sample_rate); |
| |
| size = asrc_get_output_FIFO_size(index); |
| for (j = 0; j < size; j++) { |
| reg = asrc_read_one_from_output_FIFO(index); |
| *(reg24) = reg; |
| reg24++; |
| reg = asrc_read_one_from_output_FIFO(index); |
| *(reg24) = reg; |
| reg24++; |
| } |
| t_size += size; |
| } |
| |
| mdelay(1); |
| size = asrc_get_output_FIFO_size(index); |
| for (j = 0; j < size; j++) { |
| reg = asrc_read_one_from_output_FIFO(index); |
| *(reg24) = reg; |
| reg24++; |
| reg = asrc_read_one_from_output_FIFO(index); |
| *(reg24) = reg; |
| reg24++; |
| } |
| t_size += size; |
| |
| params->output_dma_total.length = t_size * params->channel_nums * 4; |
| params->output_last_period.length = 0; |
| |
| complete(¶ms->input_complete); |
| } |
| #else |
| static void mxc_asrc_submit_dma(struct asrc_pair_params *params) |
| { |
| enum asrc_pair_index index = params->index; |
| u32 size = asrc_get_output_FIFO_size(params->index); |
| int i, j; |
| |
| /* Read all data in OUTPUT FIFO */ |
| while (size) { |
| for (j = 0; j < size; j++) |
| for (i = 0; i < params->channel_nums; i++) |
| asrc_read_one_from_output_FIFO(index); |
| /* Fetch the data every 100us */ |
| udelay(100); |
| |
| size = asrc_get_output_FIFO_size(index); |
| } |
| |
| /* Submit dma request */ |
| dmaengine_submit(params->desc_in); |
| dma_async_issue_pending(params->desc_in->chan); |
| |
| dmaengine_submit(params->desc_out); |
| dma_async_issue_pending(params->desc_out->chan); |
| |
| /* |
| * Clear dma request during the stall state of ASRC: |
| * During STALL state, the remaining in input fifo would never be |
| * smaller than the input threshold while the output fifo would not |
| * be bigger than output one. Thus the dma request would be cleared. |
| */ |
| asrc_set_watermark(index, ASRC_FIFO_THRESHOLD_MIN, ASRC_FIFO_THRESHOLD_MAX); |
| |
| /* Update the real input threshold to raise dma request */ |
| asrc_set_watermark(index, params->input_wm, params->output_wm); |
| } |
| #endif |
| |
| static long asrc_ioctl_req_pair(struct asrc_pair_params *params, |
| void __user *user) |
| { |
| unsigned long lock_flags; |
| struct asrc_req req; |
| long ret; |
| |
| ret = copy_from_user(&req, user, sizeof(req)); |
| if (ret) { |
| dev_err(asrc->dev, "failed to get req from user space: %ld\n", ret); |
| return ret; |
| } |
| |
| ret = asrc_req_pair(req.chn_num, &req.index); |
| if (ret) { |
| dev_err(asrc->dev, "failed to request pair: %ld\n", ret); |
| return ret; |
| } |
| |
| spin_lock_irqsave(&pair_lock, lock_flags); |
| params->pair_hold = 1; |
| spin_unlock_irqrestore(&pair_lock, lock_flags); |
| params->index = req.index; |
| params->channel_nums = req.chn_num; |
| |
| ret = copy_to_user(user, &req, sizeof(req)); |
| if (ret) { |
| dev_err(asrc->dev, "failed to send req to user space: %ld\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static long asrc_ioctl_config_pair(struct asrc_pair_params *params, |
| void __user *user) |
| { |
| struct asrc_config config; |
| enum asrc_pair_index index; |
| long ret; |
| |
| ret = copy_from_user(&config, user, sizeof(config)); |
| if (ret) { |
| dev_err(asrc->dev, "failed to get config from user space: %ld\n", ret); |
| return ret; |
| } |
| |
| index = config.pair; |
| |
| ret = asrc_config_pair(&config); |
| if (ret) { |
| pair_err("failed to config pair: %ld\n", ret); |
| return ret; |
| } |
| |
| params->input_wm = 4; |
| params->output_wm = 2; |
| |
| ret = asrc_set_watermark(index, params->input_wm, params->output_wm); |
| if (ret) |
| return ret; |
| |
| params->output_buffer_size = config.dma_buffer_size; |
| params->input_buffer_size = config.dma_buffer_size; |
| if (config.buffer_num > ASRC_DMA_BUFFER_NUM) |
| params->buffer_num = ASRC_DMA_BUFFER_NUM; |
| else |
| params->buffer_num = config.buffer_num; |
| |
| params->input_dma_total.length = ASRC_DMA_BUFFER_SIZE; |
| params->output_dma_total.length = ASRC_DMA_BUFFER_SIZE; |
| |
| params->input_word_width = config.input_word_width; |
| params->output_word_width = config.output_word_width; |
| |
| params->input_sample_rate = config.input_sample_rate; |
| params->output_sample_rate = config.output_sample_rate; |
| |
| if (params->output_sample_rate > params->input_sample_rate) |
| params->last_period_sample = ASRC_OUTPUT_LAST_SAMPLE_DEFAULT_MAX; |
| else |
| params->last_period_sample = ASRC_OUTPUT_LAST_SAMPLE_DEFAULT; |
| |
| |
| ret = mxc_allocate_dma_buf(params); |
| if (ret) { |
| pair_err("failed to allocate dma buffer: %ld\n", ret); |
| return ret; |
| } |
| |
| /* Request DMA channel for both input and output */ |
| params->input_dma_channel = imx_asrc_get_dma_channel(index, true); |
| if (params->input_dma_channel == NULL) { |
| pair_err("failed to request input task dma channel\n"); |
| return -EBUSY; |
| } |
| |
| params->output_dma_channel = imx_asrc_get_dma_channel(index, false); |
| if (params->output_dma_channel == NULL) { |
| pair_err("failed to request output task dma channel\n"); |
| return -EBUSY; |
| } |
| |
| ret = copy_to_user(user, &config, sizeof(config)); |
| if (ret) { |
| pair_err("failed to send config to user space: %ld\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static long asrc_ioctl_release_pair(struct asrc_pair_params *params, |
| void __user *user) |
| { |
| enum asrc_pair_index index; |
| unsigned long lock_flags; |
| long ret; |
| |
| ret = copy_from_user(&index, user, sizeof(index)); |
| if (ret) { |
| dev_err(asrc->dev, "failed to get index from user space: %ld\n", ret); |
| return ret; |
| } |
| |
| /* index might be not valid due to some application failure. */ |
| if (index < 0) |
| return -EINVAL; |
| |
| params->asrc_active = 0; |
| |
| spin_lock_irqsave(&pair_lock, lock_flags); |
| params->pair_hold = 0; |
| spin_unlock_irqrestore(&pair_lock, lock_flags); |
| |
| if (params->input_dma_channel) |
| dma_release_channel(params->input_dma_channel); |
| if (params->output_dma_channel) |
| dma_release_channel(params->output_dma_channel); |
| mxc_free_dma_buf(params); |
| asrc_release_pair(index); |
| asrc_finish_conv(index); |
| |
| return 0; |
| } |
| |
| static long asrc_ioctl_convert(struct asrc_pair_params *params, |
| void __user *user) |
| { |
| enum asrc_pair_index index = params->index; |
| struct asrc_convert_buffer buf; |
| long ret; |
| |
| ret = copy_from_user(&buf, user, sizeof(buf)); |
| if (ret) { |
| pair_err("failed to get buf from user space: %ld\n", ret); |
| return ret; |
| } |
| |
| ret = mxc_asrc_prepare_buffer(params, &buf); |
| if (ret) { |
| pair_err("failed to prepare buffer: %ld\n", ret); |
| return ret; |
| } |
| |
| #ifdef ASRC_POLLING_WITHOUT_DMA |
| asrc_polling_debug(params); |
| #else |
| mxc_asrc_submit_dma(params); |
| #endif |
| |
| ret = mxc_asrc_process_buffer(params, &buf); |
| if (ret) { |
| pair_err("failed to process buffer: %ld\n", ret); |
| return ret; |
| } |
| |
| ret = copy_to_user(user, &buf, sizeof(buf)); |
| if (ret) { |
| pair_err("failed to send buf to user space: %ld\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static long asrc_ioctl_start_conv(struct asrc_pair_params *params, |
| void __user *user) |
| { |
| enum asrc_pair_index index; |
| long ret; |
| |
| ret = copy_from_user(&index, user, sizeof(index)); |
| if (ret) { |
| dev_err(asrc->dev, "failed to get index from user space: %ld\n", ret); |
| return ret; |
| } |
| |
| params->asrc_active = 1; |
| asrc_start_conv(index); |
| |
| return 0; |
| } |
| |
| static long asrc_ioctl_stop_conv(struct asrc_pair_params *params, |
| void __user *user) |
| { |
| enum asrc_pair_index index; |
| long ret; |
| |
| ret = copy_from_user(&index, user, sizeof(index)); |
| if (ret) { |
| dev_err(asrc->dev, "failed to get index from user space: %ld\n", ret); |
| return ret; |
| } |
| |
| dmaengine_terminate_all(params->input_dma_channel); |
| dmaengine_terminate_all(params->output_dma_channel); |
| |
| asrc_stop_conv(index); |
| params->asrc_active = 0; |
| |
| return 0; |
| } |
| |
| static long asrc_ioctl_status(struct asrc_pair_params *params, |
| void __user *user) |
| { |
| enum asrc_pair_index index = params->index; |
| struct asrc_status_flags flags; |
| long ret; |
| |
| ret = copy_from_user(&flags, user, sizeof(flags)); |
| if (ret) { |
| pair_err("failed to get flags from user space: %ld\n", ret); |
| return ret; |
| } |
| |
| asrc_get_status(&flags); |
| |
| ret = copy_to_user(user, &flags, sizeof(flags)); |
| if (ret) { |
| pair_err("failed to send flags to user space: %ld\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static long asrc_ioctl_flush(struct asrc_pair_params *params, |
| void __user *user) |
| { |
| enum asrc_pair_index index = params->index; |
| init_completion(¶ms->input_complete); |
| init_completion(¶ms->output_complete); |
| |
| /* Release DMA and request again */ |
| dma_release_channel(params->input_dma_channel); |
| dma_release_channel(params->output_dma_channel); |
| |
| params->input_dma_channel = imx_asrc_get_dma_channel(index, true); |
| if (params->input_dma_channel == NULL) { |
| pair_err("failed to request input task dma channel\n"); |
| return -EBUSY; |
| } |
| |
| params->output_dma_channel = imx_asrc_get_dma_channel(index, false); |
| if (params->output_dma_channel == NULL) { |
| pair_err("failed to request output task dma channel\n"); |
| return -EBUSY; |
| } |
| |
| return 0; |
| } |
| |
| static long asrc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
| { |
| struct asrc_pair_params *params = file->private_data; |
| void __user *user = (void __user *)arg; |
| long ret = 0; |
| |
| switch (cmd) { |
| case ASRC_REQ_PAIR: |
| ret = asrc_ioctl_req_pair(params, user); |
| break; |
| case ASRC_CONFIG_PAIR: |
| ret = asrc_ioctl_config_pair(params, user); |
| break; |
| case ASRC_RELEASE_PAIR: |
| ret = asrc_ioctl_release_pair(params, user); |
| break; |
| case ASRC_CONVERT: |
| ret = asrc_ioctl_convert(params, user); |
| break; |
| case ASRC_START_CONV: |
| ret = asrc_ioctl_start_conv(params, user); |
| dump_regs(); |
| break; |
| case ASRC_STOP_CONV: |
| ret = asrc_ioctl_stop_conv(params, user); |
| break; |
| case ASRC_STATUS: |
| ret = asrc_ioctl_status(params, user); |
| break; |
| case ASRC_FLUSH: |
| ret = asrc_ioctl_flush(params, user); |
| break; |
| default: |
| dev_err(asrc->dev, "invalid ioctl cmd!\n"); |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int mxc_asrc_open(struct inode *inode, struct file *file) |
| { |
| struct asrc_pair_params *params; |
| unsigned long lock_flags; |
| int i = 0, ret = 0; |
| |
| ret = signal_pending(current); |
| if (ret) { |
| dev_err(asrc->dev, "current process has a signal pending\n"); |
| return ret; |
| } |
| |
| params = kzalloc(sizeof(struct asrc_pair_params), GFP_KERNEL); |
| if (params == NULL) { |
| dev_err(asrc->dev, "failed to allocate pair_params\n"); |
| return -ENOBUFS; |
| } |
| |
| file->private_data = params; |
| |
| while (asrc->params[i]) |
| i++; |
| |
| if (i >= ASRC_PAIR_MAX_NUM) { |
| dev_err(asrc->dev, "All pairs are being occupied\n"); |
| return -EBUSY; |
| } |
| |
| init_completion(¶ms->input_complete); |
| init_completion(¶ms->output_complete); |
| |
| spin_lock_irqsave(&pair_lock, lock_flags); |
| asrc->params[i] = params; |
| spin_unlock_irqrestore(&pair_lock, lock_flags); |
| |
| return ret; |
| } |
| |
| static int mxc_asrc_close(struct inode *inode, struct file *file) |
| { |
| struct asrc_pair_params *params; |
| unsigned long lock_flags; |
| int i; |
| |
| params = file->private_data; |
| |
| spin_lock_irqsave(&pair_lock, lock_flags); |
| for (i = 0; i < ASRC_PAIR_MAX_NUM; i++) |
| if (asrc->params[i] == params) |
| asrc->params[i] = NULL; |
| spin_unlock_irqrestore(&pair_lock, lock_flags); |
| |
| if (!params) |
| return 0; |
| |
| if (params->asrc_active) { |
| params->asrc_active = 0; |
| |
| dmaengine_terminate_all(params->input_dma_channel); |
| dmaengine_terminate_all(params->output_dma_channel); |
| |
| asrc_stop_conv(params->index); |
| |
| complete(¶ms->input_complete); |
| complete(¶ms->output_complete); |
| } |
| |
| spin_lock_irqsave(&pair_lock, lock_flags); |
| if (params->pair_hold) { |
| params->pair_hold = 0; |
| spin_unlock_irqrestore(&pair_lock, lock_flags); |
| |
| if (params->input_dma_channel) |
| dma_release_channel(params->input_dma_channel); |
| if (params->output_dma_channel) |
| dma_release_channel(params->output_dma_channel); |
| |
| mxc_free_dma_buf(params); |
| |
| asrc_release_pair(params->index); |
| asrc_finish_conv(params->index); |
| } else { |
| spin_unlock_irqrestore(&pair_lock, lock_flags); |
| } |
| |
| spin_lock_irqsave(&pair_lock, lock_flags); |
| kfree(params); |
| spin_unlock_irqrestore(&pair_lock, lock_flags); |
| file->private_data = NULL; |
| |
| return 0; |
| } |
| |
| static int mxc_asrc_mmap(struct file *file, struct vm_area_struct *vma) |
| { |
| unsigned long size = vma->vm_end - vma->vm_start; |
| int ret; |
| |
| vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); |
| |
| ret = remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, |
| size, vma->vm_page_prot); |
| if (ret) { |
| dev_err(asrc->dev, "failed to memory map!\n"); |
| return ret; |
| } |
| |
| vma->vm_flags &= ~VM_IO; |
| |
| return ret; |
| } |
| |
| static const struct file_operations asrc_fops = { |
| .owner = THIS_MODULE, |
| .unlocked_ioctl = asrc_ioctl, |
| .mmap = mxc_asrc_mmap, |
| .open = mxc_asrc_open, |
| .release = mxc_asrc_close, |
| }; |
| |
| static struct miscdevice asrc_miscdev = { |
| .name = "mxc_asrc", |
| .fops = &asrc_fops, |
| .minor = MISC_DYNAMIC_MINOR, |
| }; |
| |
| static int asrc_read_proc_attr(struct file *file, char __user *buf, |
| size_t count, loff_t *off) |
| { |
| char tmpbuf[80]; |
| int len = 0; |
| u32 reg; |
| |
| if (*off) |
| return 0; |
| |
| regmap_read(asrc->regmap, REG_ASRCNCR, ®); |
| |
| len += sprintf(tmpbuf, "ANCA: %d\nANCB: %d\nANCC: %d\n", |
| ASRCNCR_ANCx_get(ASRC_PAIR_A, reg, asrc->channel_bits), |
| ASRCNCR_ANCx_get(ASRC_PAIR_B, reg, asrc->channel_bits), |
| ASRCNCR_ANCx_get(ASRC_PAIR_C, reg, asrc->channel_bits)); |
| |
| if (len > count) |
| return 0; |
| |
| if (copy_to_user(buf, &tmpbuf, len)) |
| return -EFAULT; |
| |
| *off += len; |
| |
| return len; |
| } |
| |
| #define ASRC_MAX_PROC_BUFFER_SIZE 63 |
| |
| static int asrc_write_proc_attr(struct file *file, const char __user *buffer, |
| size_t count, loff_t *data) |
| { |
| char buf[ASRC_MAX_PROC_BUFFER_SIZE]; |
| int na, nb, nc; |
| int total; |
| |
| if (count > ASRC_MAX_PROC_BUFFER_SIZE) { |
| dev_err(asrc->dev, "proc write: the input string was too long\n"); |
| return -EINVAL; |
| } |
| |
| if (copy_from_user(buf, buffer, count)) { |
| dev_err(asrc->dev, "proc write: failed to copy buffer from user\n"); |
| return -EFAULT; |
| } |
| |
| sscanf(buf, "ANCA: %d\nANCB: %d\nANCC: %d", &na, &nb, &nc); |
| |
| total = asrc->channel_bits > 3 ? 10 : 5; |
| |
| if (na + nb + nc > total) { |
| dev_err(asrc->dev, "don't surpass %d for total\n", total); |
| return -EINVAL; |
| } else if (asrc->channel_bits < 4 && |
| (na % 2 != 0 || nb % 2 != 0 || nc % 2 != 0)) { |
| dev_err(asrc->dev, "please set an even number for each pair\n"); |
| return -EINVAL; |
| } else if (na < 0 || nb < 0 || nc < 0) { |
| dev_err(asrc->dev, "please set an positive number for each pair\n"); |
| return -EINVAL; |
| } |
| |
| |
| asrc->asrc_pair[ASRC_PAIR_A].chn_max = na; |
| asrc->asrc_pair[ASRC_PAIR_B].chn_max = nb; |
| asrc->asrc_pair[ASRC_PAIR_C].chn_max = nc; |
| |
| /* Update channel number settings */ |
| regmap_update_bits(asrc->regmap, REG_ASRCNCR, |
| ASRCNCR_ANCx_MASK(ASRC_PAIR_A, asrc->channel_bits), |
| ASRCNCR_ANCx_set(ASRC_PAIR_A, na, asrc->channel_bits)); |
| regmap_update_bits(asrc->regmap, REG_ASRCNCR, |
| ASRCNCR_ANCx_MASK(ASRC_PAIR_B, asrc->channel_bits), |
| ASRCNCR_ANCx_set(ASRC_PAIR_B, nb, asrc->channel_bits)); |
| regmap_update_bits(asrc->regmap, REG_ASRCNCR, |
| ASRCNCR_ANCx_MASK(ASRC_PAIR_C, asrc->channel_bits), |
| ASRCNCR_ANCx_set(ASRC_PAIR_C, nc, asrc->channel_bits)); |
| |
| return count; |
| } |
| |
| static const struct file_operations asrc_proc_fops = { |
| .read = asrc_read_proc_attr, |
| .write = asrc_write_proc_attr, |
| }; |
| |
| static void asrc_proc_create(void) |
| { |
| struct proc_dir_entry *proc_attr; |
| |
| asrc->proc_asrc = proc_mkdir(ASRC_PROC_PATH, NULL); |
| if (!asrc->proc_asrc) { |
| dev_err(asrc->dev, "failed to create proc entry %s\n", ASRC_PROC_PATH); |
| return; |
| } |
| |
| proc_attr = proc_create("ChSettings", S_IFREG | S_IRUGO | S_IWUSR, |
| asrc->proc_asrc, &asrc_proc_fops); |
| if (!proc_attr) { |
| remove_proc_entry(ASRC_PROC_PATH, NULL); |
| dev_err(asrc->dev, "failed to create proc attribute entry\n"); |
| } |
| } |
| |
| static void asrc_proc_remove(void) |
| { |
| remove_proc_entry("ChSettings", asrc->proc_asrc); |
| remove_proc_entry(ASRC_PROC_PATH, NULL); |
| } |
| |
| |
| static bool asrc_readable_reg(struct device *dev, unsigned int reg) |
| { |
| switch (reg) { |
| case REG_ASRCTR: |
| case REG_ASRIER: |
| case REG_ASRCNCR: |
| case REG_ASRCFG: |
| case REG_ASRCSR: |
| case REG_ASRCDR1: |
| case REG_ASRCDR2: |
| case REG_ASRSTR: |
| case REG_ASRPM1: |
| case REG_ASRPM2: |
| case REG_ASRPM3: |
| case REG_ASRPM4: |
| case REG_ASRPM5: |
| case REG_ASRTFR1: |
| case REG_ASRCCR: |
| case REG_ASRDOA: |
| case REG_ASRDOB: |
| case REG_ASRDOC: |
| case REG_ASRIDRHA: |
| case REG_ASRIDRLA: |
| case REG_ASRIDRHB: |
| case REG_ASRIDRLB: |
| case REG_ASRIDRHC: |
| case REG_ASRIDRLC: |
| case REG_ASR76K: |
| case REG_ASR56K: |
| case REG_ASRMCRA: |
| case REG_ASRFSTA: |
| case REG_ASRMCRB: |
| case REG_ASRFSTB: |
| case REG_ASRMCRC: |
| case REG_ASRFSTC: |
| case REG_ASRMCR1A: |
| case REG_ASRMCR1B: |
| case REG_ASRMCR1C: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static bool asrc_volatile_reg(struct device *dev, unsigned int reg) |
| { |
| switch (reg) { |
| case REG_ASRSTR: |
| case REG_ASRDIA: |
| case REG_ASRDIB: |
| case REG_ASRDIC: |
| case REG_ASRDOA: |
| case REG_ASRDOB: |
| case REG_ASRDOC: |
| case REG_ASRFSTA: |
| case REG_ASRFSTB: |
| case REG_ASRFSTC: |
| case REG_ASRCFG: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static bool asrc_writeable_reg(struct device *dev, unsigned int reg) |
| { |
| switch (reg) { |
| case REG_ASRCTR: |
| case REG_ASRIER: |
| case REG_ASRCNCR: |
| case REG_ASRCFG: |
| case REG_ASRCSR: |
| case REG_ASRCDR1: |
| case REG_ASRCDR2: |
| case REG_ASRSTR: |
| case REG_ASRPM1: |
| case REG_ASRPM2: |
| case REG_ASRPM3: |
| case REG_ASRPM4: |
| case REG_ASRPM5: |
| case REG_ASRTFR1: |
| case REG_ASRCCR: |
| case REG_ASRDIA: |
| case REG_ASRDIB: |
| case REG_ASRDIC: |
| case REG_ASRIDRHA: |
| case REG_ASRIDRLA: |
| case REG_ASRIDRHB: |
| case REG_ASRIDRLB: |
| case REG_ASRIDRHC: |
| case REG_ASRIDRLC: |
| case REG_ASR76K: |
| case REG_ASR56K: |
| case REG_ASRMCRA: |
| case REG_ASRMCRB: |
| case REG_ASRMCRC: |
| case REG_ASRMCR1A: |
| case REG_ASRMCR1B: |
| case REG_ASRMCR1C: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static struct regmap_config asrc_regmap_config = { |
| .reg_bits = 32, |
| .reg_stride = 4, |
| .val_bits = 32, |
| |
| .max_register = REG_ASRMCR1C, |
| .readable_reg = asrc_readable_reg, |
| .volatile_reg = asrc_volatile_reg, |
| .writeable_reg = asrc_writeable_reg, |
| .cache_type = REGCACHE_RBTREE, |
| }; |
| |
| static int mxc_asrc_probe(struct platform_device *pdev) |
| { |
| const struct of_device_id *of_id = of_match_device(fsl_asrc_ids, &pdev->dev); |
| struct device_node *np = pdev->dev.of_node; |
| enum mxc_asrc_type devtype; |
| struct resource *res; |
| void __iomem *regs; |
| int ret; |
| |
| /* Check if the device is existed */ |
| if (!np) |
| return -ENODEV; |
| |
| asrc = devm_kzalloc(&pdev->dev, sizeof(struct asrc_data), GFP_KERNEL); |
| if (!asrc) |
| return -ENOMEM; |
| |
| if (of_id) { |
| const struct platform_device_id *id_entry = of_id->data; |
| devtype = id_entry->driver_data; |
| } else { |
| devtype = pdev->id_entry->driver_data; |
| } |
| |
| asrc->dev = &pdev->dev; |
| asrc->dev->coherent_dma_mask = DMA_BIT_MASK(32); |
| |
| asrc->asrc_pair[ASRC_PAIR_A].chn_max = 2; |
| asrc->asrc_pair[ASRC_PAIR_B].chn_max = 6; |
| asrc->asrc_pair[ASRC_PAIR_C].chn_max = 2; |
| asrc->asrc_pair[ASRC_PAIR_A].overload_error = 0; |
| asrc->asrc_pair[ASRC_PAIR_B].overload_error = 0; |
| asrc->asrc_pair[ASRC_PAIR_C].overload_error = 0; |
| |
| /* Map the address */ |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (IS_ERR(res)) { |
| dev_err(&pdev->dev, "could not determine device resources\n"); |
| return PTR_ERR(res); |
| } |
| |
| regs = devm_ioremap_resource(&pdev->dev, res); |
| if (IS_ERR(regs)) { |
| dev_err(&pdev->dev, "could not map device resources\n"); |
| return PTR_ERR(regs); |
| } |
| asrc->paddr = res->start; |
| |
| /* Register regmap and let it prepare core clock */ |
| asrc->regmap = devm_regmap_init_mmio_clk(&pdev->dev, |
| "mem", regs, &asrc_regmap_config); |
| if (IS_ERR(asrc->regmap)) { |
| dev_err(&pdev->dev, "regmap init failed\n"); |
| return PTR_ERR(asrc->regmap); |
| } |
| |
| asrc->irq = platform_get_irq(pdev, 0); |
| if (asrc->irq == NO_IRQ) { |
| dev_err(&pdev->dev, "no irq for node %s\n", np->full_name); |
| return asrc->irq; |
| } |
| |
| ret = devm_request_irq(&pdev->dev, asrc->irq, asrc_isr, 0, np->name, NULL); |
| if (ret) { |
| dev_err(&pdev->dev, "could not claim irq %u: %d\n", asrc->irq, ret); |
| return ret; |
| } |
| |
| asrc->mem_clk = devm_clk_get(&pdev->dev, "mem"); |
| if (IS_ERR(asrc->mem_clk)) { |
| dev_err(&pdev->dev, "failed to get mem clock\n"); |
| return PTR_ERR(asrc->ipg_clk); |
| } |
| |
| asrc->ipg_clk = devm_clk_get(&pdev->dev, "ipg"); |
| if (IS_ERR(asrc->ipg_clk)) { |
| dev_err(&pdev->dev, "failed to get ipg clock\n"); |
| return PTR_ERR(asrc->ipg_clk); |
| } |
| |
| asrc->asrck_clk = devm_clk_get(&pdev->dev, "asrck"); |
| if (IS_ERR(asrc->asrck_clk)) { |
| dev_err(&pdev->dev, "failed to get asrck clock\n"); |
| return PTR_ERR(asrc->asrck_clk); |
| } |
| |
| asrc->dma_clk = devm_clk_get(&pdev->dev, "dma"); |
| if (IS_ERR(asrc->dma_clk)) { |
| dev_err(&pdev->dev, "failed to get dma script clock\n"); |
| return PTR_ERR(asrc->dma_clk); |
| } |
| |
| switch (devtype) { |
| case IMX35_ASRC: |
| asrc->channel_bits = 3; |
| input_clk_map = input_clk_map_v1; |
| output_clk_map = output_clk_map_v1; |
| break; |
| case IMX53_ASRC: |
| asrc->channel_bits = 4; |
| input_clk_map = input_clk_map_v2; |
| output_clk_map = output_clk_map_v2; |
| break; |
| default: |
| dev_err(&pdev->dev, "unsupported device type\n"); |
| return -EINVAL; |
| } |
| |
| ret = misc_register(&asrc_miscdev); |
| if (ret) { |
| dev_err(&pdev->dev, "failed to register char device %d\n", ret); |
| return ret; |
| } |
| |
| asrc_proc_create(); |
| |
| ret = mxc_init_asrc(); |
| if (ret) { |
| dev_err(&pdev->dev, "failed to init asrc %d\n", ret); |
| goto err_misc; |
| } |
| |
| dev_info(&pdev->dev, "mxc_asrc registered\n"); |
| |
| return ret; |
| |
| err_misc: |
| misc_deregister(&asrc_miscdev); |
| |
| return ret; |
| } |
| |
| static int mxc_asrc_remove(struct platform_device *pdev) |
| { |
| asrc_proc_remove(); |
| misc_deregister(&asrc_miscdev); |
| |
| return 0; |
| } |
| |
| #if CONFIG_PM_SLEEP |
| static int mxc_asrc_suspend(struct device *dev) |
| { |
| struct asrc_pair_params *params; |
| unsigned long lock_flags; |
| int i; |
| |
| for (i = 0; i < ASRC_PAIR_MAX_NUM; i++) { |
| spin_lock_irqsave(&pair_lock, lock_flags); |
| |
| params = asrc->params[i]; |
| if (!params || !params->pair_hold) { |
| spin_unlock_irqrestore(&pair_lock, lock_flags); |
| continue; |
| } |
| |
| if (!completion_done(¶ms->input_complete)) { |
| if (params->input_dma_channel) |
| dmaengine_terminate_all(params->input_dma_channel); |
| asrc_input_dma_callback((void *)params); |
| } |
| if (!completion_done(¶ms->output_complete)) { |
| if (params->output_dma_channel) |
| dmaengine_terminate_all(params->output_dma_channel); |
| asrc_output_dma_callback((void *)params); |
| } |
| |
| spin_unlock_irqrestore(&pair_lock, lock_flags); |
| } |
| |
| regcache_cache_only(asrc->regmap, true); |
| regcache_mark_dirty(asrc->regmap); |
| |
| return 0; |
| } |
| |
| static int mxc_asrc_resume(struct device *dev) |
| { |
| u32 asrctr; |
| |
| /* Stop all pairs provisionally */ |
| regmap_read(asrc->regmap, REG_ASRCTR, &asrctr); |
| regmap_update_bits(asrc->regmap, REG_ASRCTR, ASRCTR_ASRCEx_ALL_MASK, 0); |
| |
| regcache_cache_only(asrc->regmap, false); |
| regcache_sync(asrc->regmap); |
| |
| /* Restart enabled pairs */ |
| regmap_update_bits(asrc->regmap, REG_ASRCTR, |
| ASRCTR_ASRCEx_ALL_MASK, asrctr); |
| |
| return 0; |
| } |
| #endif /* CONFIG_PM_SLEEP */ |
| |
| static const struct dev_pm_ops mxc_asrc_pm_ops = { |
| SET_SYSTEM_SLEEP_PM_OPS(mxc_asrc_suspend, mxc_asrc_resume) |
| }; |
| |
| static struct platform_driver mxc_asrc_driver = { |
| .driver = { |
| .name = "mxc_asrc", |
| .of_match_table = fsl_asrc_ids, |
| .pm = &mxc_asrc_pm_ops, |
| }, |
| .probe = mxc_asrc_probe, |
| .remove = mxc_asrc_remove, |
| }; |
| |
| module_platform_driver(mxc_asrc_driver); |
| |
| MODULE_AUTHOR("Freescale Semiconductor, Inc."); |
| MODULE_DESCRIPTION("Asynchronous Sample Rate Converter"); |
| MODULE_LICENSE("GPL"); |
| MODULE_ALIAS("platform:mxc_asrc"); |