0001
0002
0003
0004
0005
0006
0007 #include <linux/clk.h>
0008 #include <linux/debugfs.h>
0009 #include <linux/errno.h>
0010 #include <linux/hwspinlock.h>
0011 #include <linux/io.h>
0012 #include <linux/module.h>
0013 #include <linux/of.h>
0014 #include <linux/platform_device.h>
0015 #include <linux/reset.h>
0016 #include <linux/slab.h>
0017 #include <linux/spinlock.h>
0018 #include <linux/types.h>
0019
0020 #include "hwspinlock_internal.h"
0021
0022 #define DRIVER_NAME "sun6i_hwspinlock"
0023
0024 #define SPINLOCK_BASE_ID 0
0025 #define SPINLOCK_SYSSTATUS_REG 0x0000
0026 #define SPINLOCK_LOCK_REGN 0x0100
0027 #define SPINLOCK_NOTTAKEN 0
0028
0029 struct sun6i_hwspinlock_data {
0030 struct hwspinlock_device *bank;
0031 struct reset_control *reset;
0032 struct clk *ahb_clk;
0033 struct dentry *debugfs;
0034 int nlocks;
0035 };
0036
0037 #ifdef CONFIG_DEBUG_FS
0038
0039 static int hwlocks_supported_show(struct seq_file *seqf, void *unused)
0040 {
0041 struct sun6i_hwspinlock_data *priv = seqf->private;
0042
0043 seq_printf(seqf, "%d\n", priv->nlocks);
0044
0045 return 0;
0046 }
0047 DEFINE_SHOW_ATTRIBUTE(hwlocks_supported);
0048
0049 static void sun6i_hwspinlock_debugfs_init(struct sun6i_hwspinlock_data *priv)
0050 {
0051 priv->debugfs = debugfs_create_dir(DRIVER_NAME, NULL);
0052 debugfs_create_file("supported", 0444, priv->debugfs, priv, &hwlocks_supported_fops);
0053 }
0054
0055 #else
0056
0057 static void sun6i_hwspinlock_debugfs_init(struct sun6i_hwspinlock_data *priv)
0058 {
0059 }
0060
0061 #endif
0062
0063 static int sun6i_hwspinlock_trylock(struct hwspinlock *lock)
0064 {
0065 void __iomem *lock_addr = lock->priv;
0066
0067 return (readl(lock_addr) == SPINLOCK_NOTTAKEN);
0068 }
0069
0070 static void sun6i_hwspinlock_unlock(struct hwspinlock *lock)
0071 {
0072 void __iomem *lock_addr = lock->priv;
0073
0074 writel(SPINLOCK_NOTTAKEN, lock_addr);
0075 }
0076
0077 static const struct hwspinlock_ops sun6i_hwspinlock_ops = {
0078 .trylock = sun6i_hwspinlock_trylock,
0079 .unlock = sun6i_hwspinlock_unlock,
0080 };
0081
0082 static void sun6i_hwspinlock_disable(void *data)
0083 {
0084 struct sun6i_hwspinlock_data *priv = data;
0085
0086 debugfs_remove_recursive(priv->debugfs);
0087 clk_disable_unprepare(priv->ahb_clk);
0088 reset_control_assert(priv->reset);
0089 }
0090
0091 static int sun6i_hwspinlock_probe(struct platform_device *pdev)
0092 {
0093 struct sun6i_hwspinlock_data *priv;
0094 struct hwspinlock *hwlock;
0095 void __iomem *io_base;
0096 u32 num_banks;
0097 int err, i;
0098
0099 io_base = devm_platform_ioremap_resource(pdev, SPINLOCK_BASE_ID);
0100 if (IS_ERR(io_base))
0101 return PTR_ERR(io_base);
0102
0103 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
0104 if (!priv)
0105 return -ENOMEM;
0106
0107 priv->ahb_clk = devm_clk_get(&pdev->dev, "ahb");
0108 if (IS_ERR(priv->ahb_clk)) {
0109 err = PTR_ERR(priv->ahb_clk);
0110 dev_err(&pdev->dev, "unable to get AHB clock (%d)\n", err);
0111 return err;
0112 }
0113
0114 priv->reset = devm_reset_control_get(&pdev->dev, "ahb");
0115 if (IS_ERR(priv->reset))
0116 return dev_err_probe(&pdev->dev, PTR_ERR(priv->reset),
0117 "unable to get reset control\n");
0118
0119 err = reset_control_deassert(priv->reset);
0120 if (err) {
0121 dev_err(&pdev->dev, "deassert reset control failure (%d)\n", err);
0122 return err;
0123 }
0124
0125 err = clk_prepare_enable(priv->ahb_clk);
0126 if (err) {
0127 dev_err(&pdev->dev, "unable to prepare AHB clk (%d)\n", err);
0128 goto clk_fail;
0129 }
0130
0131
0132
0133
0134
0135
0136
0137
0138
0139
0140
0141
0142
0143
0144
0145
0146 num_banks = readl(io_base + SPINLOCK_SYSSTATUS_REG) >> 28;
0147 switch (num_banks) {
0148 case 1 ... 4:
0149 priv->nlocks = 1 << (4 + num_banks);
0150 break;
0151 default:
0152 err = -EINVAL;
0153 dev_err(&pdev->dev, "unsupported hwspinlock setup (%d)\n", num_banks);
0154 goto bank_fail;
0155 }
0156
0157 priv->bank = devm_kzalloc(&pdev->dev, struct_size(priv->bank, lock, priv->nlocks),
0158 GFP_KERNEL);
0159 if (!priv->bank) {
0160 err = -ENOMEM;
0161 goto bank_fail;
0162 }
0163
0164 for (i = 0; i < priv->nlocks; ++i) {
0165 hwlock = &priv->bank->lock[i];
0166 hwlock->priv = io_base + SPINLOCK_LOCK_REGN + sizeof(u32) * i;
0167 }
0168
0169
0170 sun6i_hwspinlock_debugfs_init(priv);
0171 if (IS_ERR(priv->debugfs))
0172 priv->debugfs = NULL;
0173
0174 err = devm_add_action_or_reset(&pdev->dev, sun6i_hwspinlock_disable, priv);
0175 if (err) {
0176 dev_err(&pdev->dev, "failed to add hwspinlock disable action\n");
0177 goto bank_fail;
0178 }
0179
0180 platform_set_drvdata(pdev, priv);
0181
0182 return devm_hwspin_lock_register(&pdev->dev, priv->bank, &sun6i_hwspinlock_ops,
0183 SPINLOCK_BASE_ID, priv->nlocks);
0184
0185 bank_fail:
0186 clk_disable_unprepare(priv->ahb_clk);
0187 clk_fail:
0188 reset_control_assert(priv->reset);
0189
0190 return err;
0191 }
0192
0193 static const struct of_device_id sun6i_hwspinlock_ids[] = {
0194 { .compatible = "allwinner,sun6i-a31-hwspinlock", },
0195 {},
0196 };
0197 MODULE_DEVICE_TABLE(of, sun6i_hwspinlock_ids);
0198
0199 static struct platform_driver sun6i_hwspinlock_driver = {
0200 .probe = sun6i_hwspinlock_probe,
0201 .driver = {
0202 .name = DRIVER_NAME,
0203 .of_match_table = sun6i_hwspinlock_ids,
0204 },
0205 };
0206 module_platform_driver(sun6i_hwspinlock_driver);
0207
0208 MODULE_LICENSE("GPL");
0209 MODULE_DESCRIPTION("SUN6I hardware spinlock driver");
0210 MODULE_AUTHOR("Wilken Gottwalt <wilken.gottwalt@posteo.net>");