7、Tegra I2C总线驱动实例

drivers/i2c/busses/i2c-tegra.c

I2C总线驱动是一个单独的驱动, 在模块的加载和卸载函数中, 只需注册和注销一个platform_driver结构体, 代码如下

static const struct of_device_id tegra_i2c_of_match[] = {

{ .compatible = "nvidia,tegra194-i2c", .data = &tegra194_i2c_hw, },

{ .compatible = "nvidia,tegra186-i2c", .data = &tegra186_i2c_hw, },

{ .compatible = "nvidia,tegra210-i2c-vi", .data = &tegra210_i2c_hw, },

{ .compatible = "nvidia,tegra210-i2c", .data = &tegra210_i2c_hw, },

{ .compatible = "nvidia,tegra124-i2c", .data = &tegra124_i2c_hw, },

{ .compatible = "nvidia,tegra114-i2c", .data = &tegra114_i2c_hw, },

{ .compatible = "nvidia,tegra30-i2c", .data = &tegra30_i2c_hw, },

{ .compatible = "nvidia,tegra20-i2c", .data = &tegra20_i2c_hw, },

{ .compatible = "nvidia,tegra20-i2c-dvc", .data = &tegra20_i2c_hw, },

{},

};

MODULE_DEVICE_TABLE(of, tegra_i2c_of_match);

static struct platform_driver tegra_i2c_driver = {

.probe = tegra_i2c_probe,

.remove = tegra_i2c_remove,

.driver = {

.name = "tegra-i2c",

.of_match_table = tegra_i2c_of_match,

.pm = &tegra_i2c_pm,

},

};

module_platform_driver(tegra_i2c_driver);

当在arch/arm/mach-tegra下创建一个名字为tegra-i2c的同名platform_device, 或者在tegra的设备树中添加了tegra_i2c_of_match匹配表兼容的节点后, 上述platform_driver中的probe() 函数会执行。

其中probe指针指向的tegra_i2c_probe() 函数将被调用, 以初始化适配器硬件、 申请适配器要的内存、 时钟、 中断等资源, 最终注册适配器, 代码如下

static int tegra_i2c_probe(struct platform_device *pdev)

{

struct tegra_i2c_dev *i2c_dev;

struct resource *res;

int err;

i2c_dev = devm_kzalloc(&pdev->dev, sizeof(*i2c_dev), GFP_KERNEL);

if (!i2c_dev)

return -ENOMEM;

platform_set_drvdata(pdev, i2c_dev);

init_completion(&i2c_dev->msg_complete);

init_completion(&i2c_dev->dma_complete);

i2c_dev->hw = of_device_get_match_data(&pdev->dev);

i2c_dev->cont_id = pdev->id;

i2c_dev->dev = &pdev->dev;

i2c_dev->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);

if (IS_ERR(i2c_dev->base))

return PTR_ERR(i2c_dev->base);

i2c_dev->base_phys = res->start;

err = platform_get_irq(pdev, 0);

if (err < 0)

return err;

i2c_dev->irq = err;

/* interrupt will be enabled during of transfer time */

irq_set_status_flags(i2c_dev->irq, IRQ_NOAUTOEN);

err = devm_request_threaded_irq(i2c_dev->dev, i2c_dev->irq,

NULL, tegra_i2c_isr,

IRQF_NO_SUSPEND | IRQF_ONESHOT,

dev_name(i2c_dev->dev), i2c_dev);

if (err)

return err;

i2c_dev->rst = devm_reset_control_get_exclusive(i2c_dev->dev, "i2c");

if (IS_ERR(i2c_dev->rst)) {

dev_err_probe(i2c_dev->dev, PTR_ERR(i2c_dev->rst),

      "failed to get reset control\n");

return PTR_ERR(i2c_dev->rst);

}

tegra_i2c_parse_dt(i2c_dev);

err = tegra_i2c_init_clocks(i2c_dev);

if (err)

return err;

err = tegra_i2c_init_dma(i2c_dev);

if (err)

goto release_clocks;

/*

 * VI I2C is in VE power domain which is not always ON and not

 * IRQ-safe.  Thus, IRQ-safe device shouldn't be attached to a

 * non IRQ-safe domain because this prevents powering off the power

 * domain.

 *

 * VI I2C device shouldn't be marked as IRQ-safe because VI I2C won't

 * be used for atomic transfers.

 */

if (!i2c_dev->is_vi)

pm_runtime_irq_safe(i2c_dev->dev);

pm_runtime_enable(i2c_dev->dev);

err = tegra_i2c_init_hardware(i2c_dev);

if (err)

goto release_rpm;

i2c_set_adapdata(&i2c_dev->adapter, i2c_dev);

i2c_dev->adapter.dev.of_node = i2c_dev->dev->of_node;

i2c_dev->adapter.dev.parent = i2c_dev->dev;

i2c_dev->adapter.retries = 1;

i2c_dev->adapter.timeout = 6 * HZ;

i2c_dev->adapter.quirks = i2c_dev->hw->quirks;

i2c_dev->adapter.owner = THIS_MODULE;

i2c_dev->adapter.class = I2C_CLASS_DEPRECATED;

i2c_dev->adapter.algo = &tegra_i2c_algo;

i2c_dev->adapter.nr = pdev->id;

if (i2c_dev->hw->supports_bus_clear)

i2c_dev->adapter.bus_recovery_info = &tegra_i2c_recovery_info;

strlcpy(i2c_dev->adapter.name, dev_name(i2c_dev->dev),

sizeof(i2c_dev->adapter.name));

err = i2c_add_numbered_adapter(&i2c_dev->adapter);

if (err)

goto release_rpm;

return 0;

release_rpm:

pm_runtime_disable(i2c_dev->dev);

tegra_i2c_release_dma(i2c_dev);

release_clocks:

tegra_i2c_release_clocks(i2c_dev);

return err;

}

static int tegra_i2c_remove(struct platform_device *pdev)

{

struct tegra_i2c_dev *i2c_dev = platform_get_drvdata(pdev);

i2c_del_adapter(&i2c_dev->adapter);

pm_runtime_force_suspend(i2c_dev->dev);

tegra_i2c_release_dma(i2c_dev);

tegra_i2c_release_clocks(i2c_dev);

return 0;

}

struct tegra_i2c_dev {

struct device *dev;

struct i2c_adapter adapter;

const struct tegra_i2c_hw_feature *hw;

struct reset_control *rst;

unsigned int cont_id;

unsigned int irq;

phys_addr_t base_phys;

void __iomem *base;

struct clk_bulk_data clocks[2];

unsigned int nclocks;

struct clk *div_clk;

u32 bus_clk_rate;

struct completion msg_complete;

size_t msg_buf_remaining;

int msg_err;

u8 *msg_buf;

struct completion dma_complete;

struct dma_chan *tx_dma_chan;

struct dma_chan *rx_dma_chan;

unsigned int dma_buf_size;

dma_addr_t dma_phys;

void *dma_buf;

bool multimaster_mode;

bool atomic_mode;

bool dma_mode;

bool msg_read;

bool is_dvc;

bool is_vi;

};

tegra_i2c_probe() 函数中的platform_set_drvdata(pdev, i2c_dev) 和i2c_set_adapdata(&i2c_dev->adapter, i2c_dev) 已经把这个结构体的实例依附到了platform_device和i2c_adapter的私有数据上了, 在其他地方只要用相应的方法就可以把这个结构体的实例取出来。

与I2C适配器对应的i2c_algorithm结构体实例为tegra_i2c_algo,给出为tegra_i2c_algo的定义。

static const struct i2c_algorithm tegra_i2c_algo = {

.master_xfer                = tegra_i2c_xfer,

.master_xfer_atomic        = tegra_i2c_xfer_atomic,

.functionality                = tegra_i2c_func,

};

static int tegra_i2c_xfer_msg(struct tegra_i2c_dev *i2c_dev,

      struct i2c_msg *msg,

      enum msg_end_type end_state)

{

unsigned long time_left, xfer_time = 100;

size_t xfer_size;

u32 int_mask;

int err;

err = tegra_i2c_flush_fifos(i2c_dev);

if (err)

return err;

i2c_dev->msg_buf = msg->buf;

i2c_dev->msg_buf_remaining = msg->len;

i2c_dev->msg_err = I2C_ERR_NONE;

i2c_dev->msg_read = !!(msg->flags & I2C_M_RD);

reinit_completion(&i2c_dev->msg_complete);

if (i2c_dev->msg_read)

xfer_size = msg->len;

else

xfer_size = msg->len + I2C_PACKET_HEADER_SIZE;

xfer_size = ALIGN(xfer_size, BYTES_PER_FIFO_WORD);

i2c_dev->dma_mode = xfer_size > I2C_PIO_MODE_PREFERRED_LEN &&

    i2c_dev->dma_buf && !i2c_dev->atomic_mode;

tegra_i2c_config_fifo_trig(i2c_dev, xfer_size);

/*

 * Transfer time in mSec = Total bits / transfer rate

 * Total bits = 9 bits per byte (including ACK bit) + Start & stop bits

 */

xfer_time += DIV_ROUND_CLOSEST(((xfer_size * 9) + 2) * MSEC_PER_SEC,

       i2c_dev->bus_clk_rate);

int_mask = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST;

tegra_i2c_unmask_irq(i2c_dev, int_mask);

if (i2c_dev->dma_mode) {

if (i2c_dev->msg_read) {

dma_sync_single_for_device(i2c_dev->dev,

   i2c_dev->dma_phys,

   xfer_size, DMA_FROM_DEVICE);

err = tegra_i2c_dma_submit(i2c_dev, xfer_size);

if (err)

return err;

} else {

dma_sync_single_for_cpu(i2c_dev->dev,

i2c_dev->dma_phys,

xfer_size, DMA_TO_DEVICE);

}

}

tegra_i2c_push_packet_header(i2c_dev, msg, end_state);

if (!i2c_dev->msg_read) {

if (i2c_dev->dma_mode) {

memcpy(i2c_dev->dma_buf + I2C_PACKET_HEADER_SIZE,

       msg->buf, msg->len);

dma_sync_single_for_device(i2c_dev->dev,

   i2c_dev->dma_phys,

   xfer_size, DMA_TO_DEVICE);

err = tegra_i2c_dma_submit(i2c_dev, xfer_size);

if (err)

return err;

} else {

tegra_i2c_fill_tx_fifo(i2c_dev);

}

}

if (i2c_dev->hw->has_per_pkt_xfer_complete_irq)

int_mask |= I2C_INT_PACKET_XFER_COMPLETE;

if (!i2c_dev->dma_mode) {

if (msg->flags & I2C_M_RD)

int_mask |= I2C_INT_RX_FIFO_DATA_REQ;

else if (i2c_dev->msg_buf_remaining)

int_mask |= I2C_INT_TX_FIFO_DATA_REQ;

}

tegra_i2c_unmask_irq(i2c_dev, int_mask);

dev_dbg(i2c_dev->dev, "unmasked IRQ: %02x\n",

i2c_readl(i2c_dev, I2C_INT_MASK));

if (i2c_dev->dma_mode) {

time_left = tegra_i2c_wait_completion(i2c_dev,

      &i2c_dev->dma_complete,

      xfer_time);

/*

 * Synchronize DMA first, since dmaengine_terminate_sync()

 * performs synchronization after the transfer's termination

 * and we want to get a completion if transfer succeeded.

 */

dmaengine_synchronize(i2c_dev->msg_read ?

      i2c_dev->rx_dma_chan :

      i2c_dev->tx_dma_chan);

dmaengine_terminate_sync(i2c_dev->msg_read ?

 i2c_dev->rx_dma_chan :

 i2c_dev->tx_dma_chan);

if (!time_left && !completion_done(&i2c_dev->dma_complete)) {

dev_err(i2c_dev->dev, "DMA transfer timed out\n");

tegra_i2c_init(i2c_dev);

return -ETIMEDOUT;

}

if (i2c_dev->msg_read && i2c_dev->msg_err == I2C_ERR_NONE) {

dma_sync_single_for_cpu(i2c_dev->dev,

i2c_dev->dma_phys,

xfer_size, DMA_FROM_DEVICE);

memcpy(i2c_dev->msg_buf, i2c_dev->dma_buf, msg->len);

}

}

time_left = tegra_i2c_wait_completion(i2c_dev, &i2c_dev->msg_complete,

      xfer_time);

tegra_i2c_mask_irq(i2c_dev, int_mask);

if (time_left == 0) {

dev_err(i2c_dev->dev, "I2C transfer timed out\n");

tegra_i2c_init(i2c_dev);

return -ETIMEDOUT;

}

dev_dbg(i2c_dev->dev, "transfer complete: %lu %d %d\n",

time_left, completion_done(&i2c_dev->msg_complete),

i2c_dev->msg_err);

i2c_dev->dma_mode = false;

err = tegra_i2c_error_recover(i2c_dev, msg);

if (err)

return err;

return 0;

}

static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],

  int num)

{

struct tegra_i2c_dev *i2c_dev = i2c_get_adapdata(adap);

int i, ret;

ret = pm_runtime_get_sync(i2c_dev->dev);

if (ret < 0) {

dev_err(i2c_dev->dev, "runtime resume failed %d\n", ret);

pm_runtime_put_noidle(i2c_dev->dev);

return ret;

}

for (i = 0; i < num; i++) {

enum msg_end_type end_type = MSG_END_STOP;

if (i < (num - 1)) {

/* check whether follow up message is coming */

if (msgs[i + 1].flags & I2C_M_NOSTART)

end_type = MSG_END_CONTINUE;

else

end_type = MSG_END_REPEAT_START;

}

ret = tegra_i2c_xfer_msg(i2c_dev, &msgs[i], end_type);

if (ret)

break;

}

pm_runtime_put(i2c_dev->dev);

return ret ?: i;

}

tegra_i2c_xfer的for循环遍历所有的i2c_msg, 而每个i2c_msg则由tegra_i2c_xfer_msg() 函数处理, 它每次发起硬件操作后, 实际上需要通过wait_for_completion_timeout() 等待传输的完成, 因此这里面就会有一个被调度出去的过程。 中断到来且I2C的包传输结束的时候, 就是唤醒这个睡眠进程的时候, 代码如下

Tegra I2C总线驱动的中断服务程序

static irqreturn_t tegra_i2c_isr(int irq, void *dev_id)

{

const u32 status_err = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST;

struct tegra_i2c_dev *i2c_dev = dev_id;

u32 status;

status = i2c_readl(i2c_dev, I2C_INT_STATUS);

if (status == 0) {

dev_warn(i2c_dev->dev, "IRQ status 0 %08x %08x %08x\n",

 i2c_readl(i2c_dev, I2C_PACKET_TRANSFER_STATUS),

 i2c_readl(i2c_dev, I2C_STATUS),

 i2c_readl(i2c_dev, I2C_CNFG));

i2c_dev->msg_err |= I2C_ERR_UNKNOWN_INTERRUPT;

goto err;

}

if (status & status_err) {

tegra_i2c_disable_packet_mode(i2c_dev);

if (status & I2C_INT_NO_ACK)

i2c_dev->msg_err |= I2C_ERR_NO_ACK;

if (status & I2C_INT_ARBITRATION_LOST)

i2c_dev->msg_err |= I2C_ERR_ARBITRATION_LOST;

goto err;

}

/*

 * I2C transfer is terminated during the bus clear, so skip

 * processing the other interrupts.

 */

if (i2c_dev->hw->supports_bus_clear && (status & I2C_INT_BUS_CLR_DONE))

goto err;

if (!i2c_dev->dma_mode) {

if (i2c_dev->msg_read && (status & I2C_INT_RX_FIFO_DATA_REQ)) {

if (tegra_i2c_empty_rx_fifo(i2c_dev)) {

/*

 * Overflow error condition: message fully sent,

 * with no XFER_COMPLETE interrupt but hardware

 * asks to transfer more.

 */

i2c_dev->msg_err |= I2C_ERR_RX_BUFFER_OVERFLOW;

goto err;

}

}

if (!i2c_dev->msg_read && (status & I2C_INT_TX_FIFO_DATA_REQ)) {

if (i2c_dev->msg_buf_remaining)

tegra_i2c_fill_tx_fifo(i2c_dev);

else

tegra_i2c_mask_irq(i2c_dev,

   I2C_INT_TX_FIFO_DATA_REQ);

}

}

i2c_writel(i2c_dev, status, I2C_INT_STATUS);

if (i2c_dev->is_dvc)

dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS);

/*

 * During message read XFER_COMPLETE interrupt is triggered prior to

 * DMA completion and during message write XFER_COMPLETE interrupt is

 * triggered after DMA completion.

 *

 * PACKETS_XFER_COMPLETE indicates completion of all bytes of transfer,

 * so forcing msg_buf_remaining to 0 in DMA mode.

 */

if (status & I2C_INT_PACKET_XFER_COMPLETE) {

if (i2c_dev->dma_mode)

i2c_dev->msg_buf_remaining = 0;

/*

 * Underflow error condition: XFER_COMPLETE before message

 * fully sent.

 */

if (WARN_ON_ONCE(i2c_dev->msg_buf_remaining)) {

i2c_dev->msg_err |= I2C_ERR_UNKNOWN_INTERRUPT;

goto err;

}

complete(&i2c_dev->msg_complete);

}

goto done;

err:

/* mask all interrupts on error */

tegra_i2c_mask_irq(i2c_dev,

   I2C_INT_NO_ACK |

   I2C_INT_ARBITRATION_LOST |

   I2C_INT_PACKET_XFER_COMPLETE |

   I2C_INT_TX_FIFO_DATA_REQ |

   I2C_INT_RX_FIFO_DATA_REQ);

if (i2c_dev->hw->supports_bus_clear)

tegra_i2c_mask_irq(i2c_dev, I2C_INT_BUS_CLR_DONE);

i2c_writel(i2c_dev, status, I2C_INT_STATUS);

if (i2c_dev->is_dvc)

dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS);

if (i2c_dev->dma_mode) {

if (i2c_dev->msg_read)

dmaengine_terminate_async(i2c_dev->rx_dma_chan);

else

dmaengine_terminate_async(i2c_dev->tx_dma_chan);

complete(&i2c_dev->dma_complete);

}

complete(&i2c_dev->msg_complete);

done:

return IRQ_HANDLED;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值