diff --git a/utils/tuning/libtuning/__init__.py b/utils/tuning/libtuning/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/utils/tuning/libtuning/modules/__init__.py b/utils/tuning/libtuning/modules/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/utils/tuning/raspberrypi.py b/utils/tuning/raspberrypi.py
new file mode 100644
index 00000000..322b9c36
--- /dev/null
+++ b/utils/tuning/raspberrypi.py
@@ -0,0 +1,37 @@
+import sys
+
+import libtuning as lt
+from libtuning.modules import AWB
+from libtuning.parsers import RaspberryPiParser
+from libtuning.generators import RaspberryPiOutput
+
+from raspberrypi.alsc import ALSC as RaspberryPiALSC
+
+tuner = lt.Camera()
+
+# These modules can also be custom modules. libtuning will come with utilities
+# for handling stuff like images, so there shouldn't be too much boilerplate
+# involved in creating custom modules, though I don't yet have a concrete
+# vision on how custom implementations of modules would look.
+tuner.add(RaspberryPiALSC)
+
+# Other tuning modules can be added like so.
+# The order that the tuning modules will be executed is determined by the order
+# that they're added.
+# This is kind of an implementation detail, but the "context" is saved
+# internally in lt.Camera, so modules that are added (and therefore executed)
+# later can use the output of the previous modules. I'm thinking that a module
+# that depends on values from another module has two modes of execution, for
+# when those values are available and another for when they're not. Not quite
+# sure concretely how to handle this yet.
+tuner.add(AWB( # module parameters
+               ))
+
+tuner.setInputType(RaspberryPiParser)
+tuner.setOutputType(RaspberryPiOutput)
+
+# The order of the output doesn't necessarily have to be the same as the order
+# of input, which is specified by the order of adding the modules above.
+tuner.setOutputOrder(AWB, ALSC)
+
+tuner.run(sys.argv)
diff --git a/utils/tuning/raspberrypi/__init__.py b/utils/tuning/raspberrypi/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/utils/tuning/raspberrypi/alsc.py b/utils/tuning/raspberrypi/alsc.py
new file mode 100644
index 00000000..ff8a02fd
--- /dev/null
+++ b/utils/tuning/raspberrypi/alsc.py
@@ -0,0 +1,16 @@
+ALSC(do_color = lt.paramsIfUnset(True), \
+               debug = [lt.debug.Plot], \
+               luminance_strength = lt.params.IfUnSet(0.5) \
+               sector_shape = (16, 12), \
+               sector_x_gradient = lt.gradients.Linear, \
+               sector_y_gradient = lt.gradients.Linear, \
+               sector_x_remainder = lt.remainder.Append, \
+               sector_y_remainder = lt.remainder.Append, \
+               sector_average_function = lt.average_functions.Mean, \
+               smoothing_function = lt.smoothing.MedianBlur, \
+               smoothing_parameters = [3], \
+               output_type = lt.type.Float, \
+               output_color_channels = [lt.color.R, lt.color.G, lt.color.B], \
+               output_luminance_channels = [lt.color.G] \
+               output_range = [0, 3.999] \
+               )
diff --git a/utils/tuning/raspberrypi_alsc_only.py b/utils/tuning/raspberrypi_alsc_only.py
new file mode 100644
index 00000000..11a1fc23
--- /dev/null
+++ b/utils/tuning/raspberrypi_alsc_only.py
@@ -0,0 +1,16 @@
+import sys
+
+import libtuning as lt
+from libtuning.modules import ALSC
+from libtuning.parsers import RaspberryPiParser
+from libtuning.generators import RaspberryPiOutput
+
+from raspberrypi.alsc import ALSC as RaspberryPiALSC
+
+tuner = lt.Camera()
+tuner.add(RaspberryPiALSC)
+tuner.setInputType(RaspberryPiParser)
+tuner.setOutputType(RaspberryPiOutput)
+tuner.setOutputOrder(ALSC)
+
+tuner.run(sys.argv)
diff --git a/utils/tuning/rkisp1.py b/utils/tuning/rkisp1.py
new file mode 100644
index 00000000..8e26beba
--- /dev/null
+++ b/utils/tuning/rkisp1.py
@@ -0,0 +1,67 @@
+import sys
+
+import libtuning as lt
+from libtuning.modules import ALSC
+from libtuning.parsers import YamlParser
+from libtuning.generators import YamlOutput
+
+tuner = lt.Camera()
+tuner.add(ALSC(do_color = lt.paramsIfUnset(True), \
+
+               # This can support other debug options (I can't think of any rn
+               # but for future-proofing)
+               debug = [lt.debug.Plot], \
+
+               # The name of IfUnSet can obviously be changed, but really I
+               # just want something that says "it must be specified in the
+               # configuration, and get the value from there" or "if the value
+               # is not in the configuration, use this"
+               luminance_strength = lt.params.IfUnSet(0.5) \
+
+               sector_shape = (16, 16), \
+
+               # Other functions might include Circular, Hyperbolic, Gaussian,
+               # Linear, Exponential, Logarithmic, etc
+               # Of course, both need not be the same function
+               # Some functions would need a sector_x_parameter (eg. sigma for Gaussian)
+               # Alternatively: sector_x_sizes = [] ? I don't think it would work tho
+               sector_x_gradient = lt.gradients.Parabolic, \
+               sector_y_gradient = lt.gradients.Parabolic, \
+
+               # This is the function that will be used to average the pixels in each sector
+               # This can also be a custom function.
+               sector_average_function = lt.average_functions.Mean, \
+
+               # This is the function that will be used to smooth the color ratio values
+               # This can also be a custom function.
+               smoothing_function = lt.smoothing.MedianBlur, \
+               smoothing_parameters = [3], \
+
+               # Are there any platforms that use integer values for their lsc table?
+               output_type = lt.type.Float, \
+
+               # Required if and only if do_color can be or is True
+               output_color_channels = [lt.color.R, lt.color.GR, lt.color.GB, lt.color.B], \
+
+               # Required if and only if do_color can be or is False
+               output_luminance_channels = [lt.color.GR, lt.color.GB], \
+
+               # Automatically get the precision from this
+               output_range = [0, 3.999] \
+
+               # Do we need a flag to enable/disable calculating sigmas? afaik
+               # the rkisp1 IPA doesn't use it? But we could output it anyway
+               # and algorithms can decide whether or not if they want to use
+               # it. Oh well, no flag for now, we can always add it later if
+               # there's a big demand, plus it's only two to three values and
+               # not an entire table.
+               ))
+tuner.setInputType(YamlParser)
+tuner.setOutputType(YamlOutput)
+tuner.setOutputOrder(ALSC)
+
+# Maybe this should be wrapped in an if __main__ = '__main__' ? That way the
+# developer can control which tuner they want to be executed based on another
+# layer of arguments? But I was thinking that this would handle *all* arguments
+# based on the modules' and the input/output configurations.
+tuner.run(sys.argv)
